Every API here is available over the APIs.io API and to AI agents over MCP.
{"openapi":"3.1.0","info":{"title":"MLOps Platform API","description":"Multi-tenant labeling-platform backend. Login is account-level (ADR-0113): `GET /auth/authorize` → hosted sign-in → `POST /auth/callback` → Bearer token. Workspace membership is resolved per-request: endpoints under `/tenants/{slug}` match the token's `sub` against that tenant's membership table; non-members are rejected with 403. The account surface (`/me/*`) lists workspaces and handles invitation accept/decline.\n\n**Error envelope** (any 4xx/5xx, ADR-0034): `{\"error\": {\"code\": str, \"message\": str, \"hint\": str|null, \"details\": object|null, \"request_id\": str}}`. The `code` field is a stable enum (`components.schemas.ErrorCode`); pattern-match on `code`, not on `message`. The `hint` field, when present, suggests a concrete next step.\n\n**Pagination** (cursor-based, see ADR-0012): list endpoints return `{\"items\": [...], \"next_cursor\": str | null}`. Pass `?cursor=` for the next page. No total counts.\n\n**Idempotency**: bulk-op POSTs accept an `Idempotency-Key` header to deduplicate retries (ADR-0023).","version":"0.1.0"},"paths":{"/tenants":{"post":{"tags":["tenants"],"summary":"Create Tenant","operationId":"tenants-create_tenant","parameters":[{"name":"x-bootstrap-secret","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Bootstrap-Secret"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TenantCreate"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TenantResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"409":{"description":"Conflict — resource state prevents this action","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate-limited (per-tenant token bucket)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/auth/authorize":{"get":{"tags":["auth"],"summary":"Authorize","description":"Return the hosted-AuthKit URL for the FE to redirect the user to.\nAccount-level: no organization scoping — workspace choice happens in-app\nafter login (`GET /me/tenants`).","operationId":"auth-authorize","parameters":[{"name":"redirect_uri","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"FE callback URL to return to after login. Must be in the allowlist; defaults to the first allowed URI.","title":"Redirect Uri"},"description":"FE callback URL to return to after login. Must be in the allowlist; defaults to the first allowed URI."},{"name":"state","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Opaque FE state, round-tripped to the callback unchanged (e.g. a return-to path). No longer carries a tenant — login is account-level (ADR-0113).","title":"State"},"description":"Opaque FE state, round-tripped to the callback unchanged (e.g. a return-to path). No longer carries a tenant — login is account-level (ADR-0113)."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AuthorizeResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"409":{"description":"Conflict — resource state prevents this action","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate-limited (per-tenant token bucket)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/auth/callback":{"post":{"tags":["auth"],"summary":"Callback","description":"Exchange the AuthKit `code` for an account-level token pair. Works for\nevery arrival — normal login, fresh sign-up, and the invitation-email\nlanding — because no tenant context is needed: the FE follows up with\n`GET /me/tenants` / `GET /me/invitations` to route the user.","operationId":"auth-callback","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CallbackRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AuthTokens"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"409":{"description":"Conflict — resource state prevents this action","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate-limited (per-tenant token bucket)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/auth/refresh":{"post":{"tags":["auth"],"summary":"Refresh","operationId":"auth-refresh","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RefreshRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AuthTokens"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"409":{"description":"Conflict — resource state prevents this action","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate-limited (per-tenant token bucket)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/auth/logout":{"post":{"tags":["auth"],"summary":"Logout","description":"Revoke the WorkOS session behind the presented access token. Idempotent —\nan invalid/expired token means there's nothing to revoke. Account-level:\nneeds no tenant, and works for an account with zero memberships.","operationId":"auth-logout","parameters":[{"name":"authorization","in":"header","required":true,"schema":{"type":"string","description":"Bearer access token (the session to revoke is read from it)","title":"Authorization"},"description":"Bearer access token (the session to revoke is read from it)"}],"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"409":{"description":"Conflict — resource state prevents this action","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate-limited (per-tenant token bucket)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/me/tenants":{"get":{"tags":["me"],"summary":"List My Tenants","description":"Every workspace this account belongs to, newest membership first.","operationId":"me-list_my_tenants","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MyTenantsResponse"}}}},"401":{"description":"Unauthenticated — missing, malformed, or expired Bearer token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Authenticated but not authorized for this resource","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"Conflict — resource state prevents this action","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate-limited (per-tenant token bucket)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"security":[{"HTTPBearer":[]}]},"post":{"tags":["me"],"summary":"Create My Tenant","description":"Create a workspace as the authenticated account, who becomes its admin.\nThe membership row copies the account's profile (email/name) from WorkOS.","operationId":"me-create_my_tenant","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TenantCreateMe"}}},"required":true},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TenantMembership"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"401":{"description":"Unauthenticated — missing, malformed, or expired Bearer token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Authenticated but not authorized for this resource","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"Conflict — resource state prevents this action","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate-limited (per-tenant token bucket)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"security":[{"HTTPBearer":[]}]}},"/me/invitations":{"get":{"tags":["me"],"summary":"List My Invitations","description":"Pending, unexpired invitations addressed to this account's email —\nthe exact-match rule (ADR-0113): the invited address must equal the\naccount's email, case-insensitively; domains don't count.","operationId":"me-list_my_invitations","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MyInvitationsResponse"}}}},"401":{"description":"Unauthenticated — missing, malformed, or expired Bearer token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Authenticated but not authorized for this resource","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"Conflict — resource state prevents this action","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate-limited (per-tenant token bucket)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"security":[{"HTTPBearer":[]}]}},"/me/invitations/{invitation_id}/accept":{"post":{"tags":["me"],"summary":"Accept Invitation","description":"Join the workspace: membership row with the invited role (profile copied\nfrom the account), WorkOS org membership mirrored, transport invitation\nrevoked. Idempotence: an already-member account gets 409 USER_EMAIL_TAKEN\nonly via a re-created invitation — this row goes terminal on success.","operationId":"me-accept_invitation","security":[{"HTTPBearer":[]}],"parameters":[{"name":"invitation_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Invitation Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TenantMembership"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"401":{"description":"Unauthenticated — missing, malformed, or expired Bearer token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Authenticated but not authorized for this resource","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"Conflict — resource state prevents this action","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate-limited (per-tenant token bucket)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/me/invitations/{invitation_id}/decline":{"post":{"tags":["me"],"summary":"Decline Invitation","description":"Turn the invitation down. Terminal; the admin can re-invite later.","operationId":"me-decline_invitation","security":[{"HTTPBearer":[]}],"parameters":[{"name":"invitation_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Invitation Id"}}],"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"401":{"description":"Unauthenticated — missing, malformed, or expired Bearer token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Authenticated but not authorized for this resource","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"Conflict — resource state prevents this action","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate-limited (per-tenant token bucket)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/tenants/{slug}/invitations":{"post":{"tags":["invitations"],"summary":"Create Invitation","description":"Invite an email into this workspace. One live invitation per email; a\nmember's email can't be re-invited. Counts toward the user quota so accepts\ncan't blow past the cap.","operationId":"invitations-create_invitation","security":[{"HTTPBearer":[]}],"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/InvitationCreate"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InvitationResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"401":{"description":"Unauthenticated — missing, malformed, or expired Bearer token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Authenticated but not authorized for this resource","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Resource not found in this tenant","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"Conflict — resource state prevents this action","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate-limited (per-tenant token bucket)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}},"get":{"tags":["invitations"],"summary":"List Invitations","operationId":"invitations-list_invitations","security":[{"HTTPBearer":[]}],"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}},{"name":"status","in":"query","required":false,"schema":{"anyOf":[{"$ref":"#/components/schemas/InvitationStatus"},{"type":"null"}],"description":"Filter to one lifecycle state (default: all, newest first).","title":"Status"},"description":"Filter to one lifecycle state (default: all, newest first)."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InvitationPage"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"401":{"description":"Unauthenticated — missing, malformed, or expired Bearer token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Authenticated but not authorized for this resource","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Resource not found in this tenant","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"Conflict — resource state prevents this action","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate-limited (per-tenant token bucket)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/tenants/{slug}/invitations/{invitation_id}/revoke":{"post":{"tags":["invitations"],"summary":"Revoke Invitation Route","description":"Withdraw a pending invitation (the emailed link dies). Terminal rows\ncan't be revoked — 409 with the current state.","operationId":"invitations-revoke_invitation_route","security":[{"HTTPBearer":[]}],"parameters":[{"name":"invitation_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Invitation Id"}},{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InvitationResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"401":{"description":"Unauthenticated — missing, malformed, or expired Bearer token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Authenticated but not authorized for this resource","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Resource not found in this tenant","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"Conflict — resource state prevents this action","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate-limited (per-tenant token bucket)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/tenants/{slug}/users":{"post":{"tags":["users"],"summary":"Create User Seeded","description":"Programmatic seeding (ADR-0113): create a ready-to-login member directly —\nWorkOS account with a password, org membership mirror, membership row. No\nemail is sent. Humans join via invitations + in-app accept (routes/me.py,\nroutes/invitations.py).","operationId":"users-create_user_seeded","security":[{"HTTPBearer":[]}],"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserSeedCreate"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"401":{"description":"Unauthenticated — missing, malformed, or expired Bearer token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Authenticated but not authorized for this resource","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Resource not found in this tenant","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"Conflict — resource state prevents this action","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate-limited (per-tenant token bucket)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}},"get":{"tags":["users"],"summary":"List Users","description":"List workspace users. `?q=` matches (case-insensitively) against email,\nfirst_name, and last_name.","operationId":"users-list_users","security":[{"HTTPBearer":[]}],"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":100,"minimum":1,"description":"Max rows per page.","default":50,"title":"Limit"},"description":"Max rows per page."},{"name":"cursor","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Opaque pagination cursor from a previous response's `next_cursor`; omit for the first page.","title":"Cursor"},"description":"Opaque pagination cursor from a previous response's `next_cursor`; omit for the first page."},{"name":"q","in":"query","required":false,"schema":{"anyOf":[{"type":"string","minLength":1,"maxLength":255},{"type":"null"}],"description":"Case-insensitive substring search (min 1 char). Wildcards are matched literally.","title":"Q"},"description":"Case-insensitive substring search (min 1 char). Wildcards are matched literally."},{"name":"role","in":"query","required":false,"schema":{"anyOf":[{"$ref":"#/components/schemas/WorkspaceRole"},{"type":"null"}],"description":"`admin` or `member`. Filters to that workspace role.","title":"Role"},"description":"`admin` or `member`. Filters to that workspace role."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserPage"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"401":{"description":"Unauthenticated — missing, malformed, or expired Bearer token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Authenticated but not authorized for this resource","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Resource not found in this tenant","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"Conflict — resource state prevents this action","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate-limited (per-tenant token bucket)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/tenants/{slug}/users/me":{"delete":{"tags":["users"],"summary":"Leave Workspace","description":"Leave this workspace (ADR-0113). The account survives; only the\nmembership goes. The last admin can't leave — hand off first.","operationId":"users-leave_workspace","security":[{"HTTPBearer":[]}],"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}}],"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"401":{"description":"Unauthenticated — missing, malformed, or expired Bearer token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Authenticated but not authorized for this resource","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Resource not found in this tenant","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"Conflict — resource state prevents this action","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate-limited (per-tenant token bucket)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}},"get":{"tags":["users"],"summary":"Get Me","operationId":"users-get_me","security":[{"HTTPBearer":[]}],"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"401":{"description":"Unauthenticated — missing, malformed, or expired Bearer token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Authenticated but not authorized for this resource","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Resource not found in this tenant","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"Conflict — resource state prevents this action","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate-limited (per-tenant token bucket)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}},"patch":{"tags":["users"],"summary":"Update Me","description":"Edit your own first/last name. Email + password have dedicated flows.","operationId":"users-update_me","security":[{"HTTPBearer":[]}],"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserSelfUpdate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"401":{"description":"Unauthenticated — missing, malformed, or expired Bearer token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Authenticated but not authorized for this resource","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Resource not found in this tenant","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"Conflict — resource state prevents this action","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate-limited (per-tenant token bucket)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/tenants/{slug}/users/me/password":{"post":{"tags":["users"],"summary":"Change My Password","description":"Change your own password. Verifies `previous_password` against the identity\nprovider (WorkOS), then sets `new_password` (subject to the provider's strength\npolicy). 401 if the current password is wrong; 422 if the new one is rejected\n(too short / common). No response body on success (204).","operationId":"users-change_my_password","security":[{"HTTPBearer":[]}],"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ChangePasswordRequest"}}}},"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"401":{"description":"Unauthenticated — missing, malformed, or expired Bearer token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Authenticated but not authorized for this resource","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Resource not found in this tenant","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"Conflict — resource state prevents this action","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate-limited (per-tenant token bucket)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/tenants/{slug}/users/{user_id}":{"patch":{"tags":["users"],"summary":"Update User Role","description":"Change a workspace user's role. Admin-only.\n\nLast-admin guard: demoting the only remaining admin (whether yourself\nor someone else) is blocked with 400 — otherwise the workspace could\nbe left with no one able to invite users, create datasets/projects, or\ndelete exports. Promote a new admin first.","operationId":"users-update_user_role","security":[{"HTTPBearer":[]}],"parameters":[{"name":"user_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"User Id"}},{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserRoleUpdate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"401":{"description":"Unauthenticated — missing, malformed, or expired Bearer token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Authenticated but not authorized for this resource","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Resource not found in this tenant","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"Conflict — resource state prevents this action","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate-limited (per-tenant token bucket)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}},"delete":{"tags":["users"],"summary":"Remove Member","description":"Remove a member from this workspace (admin; ADR-0113). Their account is\nuntouched — only the membership (and its API keys, via cascade) goes.\nRemoving yourself is `DELETE /users/me` (leave); the last admin is guarded\neither way.","operationId":"users-remove_member","security":[{"HTTPBearer":[]}],"parameters":[{"name":"user_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"User Id"}},{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}}],"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"401":{"description":"Unauthenticated — missing, malformed, or expired Bearer token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Authenticated but not authorized for this resource","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Resource not found in this tenant","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"Conflict — resource state prevents this action","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate-limited (per-tenant token bucket)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/tenants/{slug}/users/me/api-keys":{"post":{"tags":["api-keys"],"summary":"Create a new API key for the calling user.","description":"Mints a new long-lived credential bound to the calling user. The plaintext is returned **once** in this response — store it securely (env var, secret manager). The server only retains an argon2id hash, so a lost plaintext cannot be recovered; revoke and re-create.\n\nUse the returned `plaintext` as a `Authorization: Bearer <plaintext>` header to authenticate subsequent requests. The key inherits the user's role; v1 has no scoping.\n\n**Capped at 1 active key per user.** To rotate, `DELETE` your existing key first, then `POST` to mint a fresh one. Without scopes there's no functional reason to hold more than one.","operationId":"api-keys-create_api_key","security":[{"HTTPBearer":[]}],"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiKeyCreate"}}}},"responses":{"201":{"description":"Successful Response","con
# --- truncated at 32 KB (524 KB total) ---
# Full source: https://raw.githubusercontent.com/api-evangelist/superb-ai/refs/heads/main/openapi/superb-ai-mlops-platform-openapi.json