Skip to content

Team — /v1/team

All team endpoints require a valid JWT session (Authorization: Bearer <jwt>). Org-level API keys are not accepted — team management is a session-only operation.

Public endpoints (/invites/:token/preview and /invites/:token/accept) require no authentication.

GET /v1/team/members
Authorization: Bearer <jwt>

Returns all members in the org, plus plan metadata.

Response

{
"members": [
{
"user_id": "usr_abc123",
"email": "[email protected]",
"role": "owner",
"email_verified": true,
"api_key_count": 2,
"created_at": "2026-06-03T00:00:00Z"
}
],
"plan": "growth",
"max_members": 15,
"admin_role": true
}

max_members is null for Enterprise (unlimited).


GET /v1/team/invites
Authorization: Bearer <jwt>

Returns pending invites that have not expired or been accepted. Requires Owner or Admin role.

Response

{
"invites": [
{
"invite_id": "inv_abc123",
"email": "[email protected]",
"role": "user",
"inviter_id": "usr_xyz",
"created_at": "2026-06-10T00:00:00Z",
"expires_at": "2026-06-17T00:00:00Z"
}
]
}

POST /v1/team/invites
Authorization: Bearer <jwt>
Content-Type: application/json

Requires Owner or Admin role. Sends an invite email with a 7-day accept link. If a pending invite already exists for this email, it is expired and a fresh one is created.

Body

{
"email": "[email protected]",
"role": "user"
}

role must be "user" or "admin". Admin role requires a Growth or above plan — returns 402 otherwise.

Response 201

{
"invite_id": "inv_abc123",
"email": "[email protected]",
"role": "user",
"expires_at": "2026-06-17T00:00:00Z"
}

Errors

StatusErrorMeaning
402Seat limit reached for plan — upgrade required
402Admin role not available on this plan
409This email is already a member

DELETE /v1/team/invites/:invite_id
Authorization: Bearer <jwt>

Requires Owner or Admin role. Permanently deletes the pending invite.

Response 200

{ "cancelled": true }

GET /v1/team/invites/:token/preview

No authentication required. Used by the accept-invite page to show org name and role before the user sets a password.

Response 200

{
"invite_id": "inv_abc123",
"email": "[email protected]",
"role": "user",
"org_name": "Acme AI",
"expires_at": "2026-06-17T00:00:00Z"
}

Returns 404 if the token is not found or has expired.


POST /v1/team/invites/:token/accept
Content-Type: application/json

No authentication required. Creates a new user account, marks the invite accepted, and returns a JWT for immediate login.

Body

{
"password": "your-password"
}

Password must be between 8 and 128 characters.

Response 201

{
"token": "<jwt>",
"user": {
"user_id": "usr_abc123",
"email": "[email protected]",
"org_id": "org_xyz",
"role": "user",
"email_verified": true
}
}

Returns 400 if the token is expired or already accepted. Returns 409 if an account with this email already exists.


PATCH /v1/team/members/:user_id/role
Authorization: Bearer <jwt>
Content-Type: application/json

Requires Owner role.

Body

{
"role": "admin"
}

role must be "owner", "admin", or "user".

Response 200

{
"user_id": "usr_abc123",
"role": "admin"
}

Returns 400 if attempting to demote the last owner. Returns 403 if the caller is not an owner.


DELETE /v1/team/members/:user_id
Authorization: Bearer <jwt>

Requires Owner or Admin role. Owners can remove anyone (except the last owner). Admins can only remove Users.

Response 200

{ "removed": true }

Returns 400 if attempting to remove yourself or the last owner.


POST /v1/team/members/:user_id/api-keys
Authorization: Bearer <jwt>

Owners and Admins can create keys for any member. Users can only create keys for themselves. The full key value is returned once and cannot be retrieved again.

Response 201

{
"key_id": "kid_abc123",
"key": "ctv_key_...",
"created_at": "2026-06-10T00:00:00Z"
}

DELETE /v1/team/members/:user_id/api-keys/:key_id
Authorization: Bearer <jwt>

Owners and Admins can revoke keys for any member. Users can only revoke their own keys. Revocation is immediate.

Response 200

{ "revoked": true }

GET /v1/team/members/me/api-keys
Authorization: Bearer <jwt>

Returns the current user’s active (non-revoked) personal API keys. Key values are masked — only a preview prefix is returned.

Response 200

{
"keys": [
{
"key_id": "kid_abc123",
"preview": "ctv_key_a1b2c3d4e5f6...",
"created_at": "2026-06-10T00:00:00Z"
}
]
}