OwlMetry
API Reference

Teams

Team management endpoints — create, rename, delete teams, invite members, and manage roles.

Team management, invitations, role management, and audit logs. All team routes require JWT authentication (user-only, not API key accessible) unless otherwise noted.

POST /v1/teams

Create a new team. The creating user becomes the owner.

Auth required: Yes (JWT only)

// Request
{
  "name": "Acme Inc",
  "slug": "acme-inc"
}

// Response (201)
{
  "id": "uuid",
  "name": "Acme Inc",
  "slug": "acme-inc",
  "created_at": "2024-01-01T00:00:00.000Z",
  "updated_at": "2024-01-01T00:00:00.000Z"
}

The slug must match ^[a-z0-9-]+$. Returns 409 if a team with the same slug already exists.

GET /v1/teams/:teamId

Get team details including members and pending invitations.

Auth required: Yes (JWT only, member role minimum)

// Response (200)
{
  "id": "uuid",
  "name": "Acme Inc",
  "slug": "acme-inc",
  "created_at": "2024-01-01T00:00:00.000Z",
  "updated_at": "2024-01-01T00:00:00.000Z",
  "members": [
    {
      "user_id": "uuid",
      "email": "[email protected]",
      "name": "Owner",
      "role": "owner",
      "joined_at": "2024-01-01T00:00:00.000Z"
    }
  ],
  "pending_invitations": [
    {
      "id": "uuid",
      "team_id": "uuid",
      "email": "[email protected]",
      "role": "member",
      "invited_by": {
        "user_id": "uuid",
        "name": "Owner",
        "email": "[email protected]"
      },
      "expires_at": "2024-01-08T00:00:00.000Z",
      "accepted_at": null,
      "created_at": "2024-01-01T00:00:00.000Z"
    }
  ]
}

PATCH /v1/teams/:teamId

Rename a team.

Auth required: Yes (JWT only, admin role required)

// Request
{
  "name": "Acme Corp"
}

// Response (200)
{
  "id": "uuid",
  "name": "Acme Corp",
  "slug": "acme-inc",
  "created_at": "2024-01-01T00:00:00.000Z",
  "updated_at": "2024-01-15T12:00:00.000Z"
}

DELETE /v1/teams/:teamId

Soft-delete a team and cascade to all children (projects, apps, API keys, metric definitions, funnel definitions). Team members and invitations are hard-deleted.

Auth required: Yes (JWT only, owner role required)

Cannot delete your only team. Returns 400 if the user has no other team membership.

// Response (200)
{
  "deleted": true
}

GET /v1/teams/:teamId/members

List all members of a team.

Auth required: Yes (JWT only, member role minimum)

// Response (200)
{
  "members": [
    {
      "user_id": "uuid",
      "email": "[email protected]",
      "name": "User",
      "role": "owner",
      "joined_at": "2024-01-01T00:00:00.000Z"
    }
  ]
}

PATCH /v1/teams/:teamId/members/:userId

Change a member's role.

Auth required: Yes (JWT only, admin role required)

Role hierarchy: owner > admin > member. Only owners can promote to owner. Admins can only change the role of members with a lower role. Cannot change your own role. Cannot demote the last owner.

// Request
{
  "role": "admin"
}

// Response (200)
{
  "user_id": "uuid",
  "role": "admin"
}

DELETE /v1/teams/:teamId/members/:userId

Remove a member from the team. If userId matches the authenticated user, this acts as "leave team."

Auth required: Yes (JWT only, admin role required for removing others)

ParameterTypeDescription
revoke_agent_keysstringQuery param. Set to "true" to soft-delete the removed member's agent API keys for this team.

Owners can remove anyone. Admins can only remove members with a lower role. Self-removal is allowed for any role (except the sole owner).

// Response (200)
{
  "removed": true,
  "revoked_agent_keys": 2
}

POST /v1/teams/:teamId/invitations

Send or resend an email invitation to join the team.

Auth required: Yes (JWT only, admin role required)

Only owners can invite as owner. Re-inviting an existing pending invite regenerates the token and resets the 7-day expiry.

// Request
{
  "email": "[email protected]",
  "role": "member"  // optional, defaults to "member"
}

// Response (201)
{
  "id": "uuid",
  "team_id": "uuid",
  "email": "[email protected]",
  "role": "member",
  "invited_by": {
    "user_id": "uuid",
    "name": "Owner",
    "email": "[email protected]"
  },
  "expires_at": "2024-01-08T00:00:00.000Z",
  "accepted_at": null,
  "created_at": "2024-01-01T00:00:00.000Z"
}

Returns 409 if the email is already a team member.

GET /v1/teams/:teamId/invitations

List pending invitations for a team.

Auth required: Yes (JWT only, member role minimum)

// Response (200)
{
  "invitations": [
    {
      "id": "uuid",
      "team_id": "uuid",
      "email": "[email protected]",
      "role": "member",
      "invited_by": {
        "user_id": "uuid",
        "name": "Owner",
        "email": "[email protected]"
      },
      "expires_at": "2024-01-08T00:00:00.000Z",
      "accepted_at": null,
      "created_at": "2024-01-01T00:00:00.000Z"
    }
  ]
}

DELETE /v1/teams/:teamId/invitations/:invitationId

Revoke (hard-delete) a pending invitation.

Auth required: Yes (JWT only, admin role required)

// Response (200)
{
  "deleted": true
}

GET /v1/invites/:token

Get public information about an invitation. No authentication required.

Returns 410 if the invitation has already been accepted or has expired.

// Response (200)
{
  "team_name": "Acme Inc",
  "team_slug": "acme-inc",
  "role": "member",
  "email": "[email protected]",
  "invited_by_name": "Owner",
  "expires_at": "2024-01-08T00:00:00.000Z"
}

POST /v1/invites/accept

Accept an invitation and join the team. The authenticated user's email must match the invitation email.

Auth required: Yes (JWT only)

// Request
{
  "token": "invitation-token-uuid"
}

// Response (200)
{
  "team_id": "uuid",
  "team_name": "Acme Inc",
  "role": "member"
}

Returns 403 if the authenticated email does not match the invitation. Returns 410 if expired or already accepted.

GET /v1/teams/:teamId/audit-logs

Query the audit log for a team. Returns a paginated list of actions performed on team resources.

Auth required: Yes (audit_logs:read permission for agent keys, or JWT with admin role)

Query parameters

ParameterTypeDescription
resource_typestringFilter by resource type: app, project, api_key, team, team_member, invitation, metric_definition, funnel_definition, user.
resource_idstringFilter by resource ID.
actor_idstringFilter by actor (user or API key) ID.
actionstringFilter by action: create, update, delete.
sincestringStart time. Relative (1h, 30m, 7d, 1w, 30s) or ISO 8601.
untilstringEnd time. Relative (1h, 30m, 7d, 1w, 30s) or ISO 8601.
cursorstringCursor from previous response (format: `timestamp
limitnumberItems per page (1-200, default 50).

Response

{
  "audit_logs": [
    {
      "id": "uuid",
      "team_id": "uuid",
      "actor_type": "user",
      "actor_id": "uuid",
      "action": "create",
      "resource_type": "app",
      "resource_id": "uuid",
      "changes": null,
      "metadata": {
        "name": "iOS App",
        "platform": "apple"
      },
      "timestamp": "2024-01-15T10:30:00.000Z"
    }
  ],
  "cursor": "2024-01-15T10:29:00.000Z|uuid",
  "has_more": true
}
FieldTypeDescription
actor_typestringuser, api_key, or system.
actor_idstringID of the user or API key that performed the action.
actionstringcreate, update, or delete.
resource_typestringType of resource affected.
resource_idstringID of the affected resource.
changesobject or nullFor update actions, contains { field: { before, after } } diffs.
metadataobject or nullAdditional context about the action.

Ready to get started?

Install the CLI and let your agent handle the rest.