{"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 ` 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","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiKeyCreated"}}}},"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":["api-keys"],"summary":"List the calling user's active API keys.","description":"Returns metadata only — never the plaintext. Use `prefix` to identify which key is which (the prefix is also visible in the API key itself, e.g. `sbd_pk_a3f4…`).","operationId":"api-keys-list_api_keys","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/ApiKeyList"}}}},"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/{key_id}":{"delete":{"tags":["api-keys"],"summary":"Revoke (soft-delete) an API key.","description":"Sets `revoked_at`; the key is rejected on the next auth check. Idempotent — revoking an already-revoked key returns 404 (the list endpoint hides revoked keys, so a duplicate revoke would be operating on stale information; better to surface than swallow).","operationId":"api-keys-revoke_api_key","security":[{"HTTPBearer":[]}],"parameters":[{"name":"key_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Key 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}/datasets":{"get":{"tags":["datasets"],"summary":"List All Datasets","operationId":"datasets-list_all_datasets","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":"created_at_from","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"description":"Inclusive lower bound (>=) on `created_at` (ISO-8601).","title":"Created At From"},"description":"Inclusive lower bound (>=) on `created_at` (ISO-8601)."},{"name":"created_at_to","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"description":"Exclusive upper bound (<) on `created_at` (ISO-8601).","title":"Created At To"},"description":"Exclusive upper bound (<) on `created_at` (ISO-8601)."},{"name":"include_total","in":"query","required":false,"schema":{"type":"boolean","description":"When true, response carries `total` = COUNT(*) for the same filter (ignores cursor/limit).","default":false,"title":"Include Total"},"description":"When true, response carries `total` = COUNT(*) for the same filter (ignores cursor/limit)."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UnifiedDatasetPage"}}}},"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}/datasets/images":{"post":{"tags":["image_datasets"],"summary":"Create Image Dataset","operationId":"image_datasets-create_image_dataset","security":[{"HTTPBearer":[]}],"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DatasetCreate"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DatasetResponse"}}}},"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}/datasets/images/{dataset_id}":{"get":{"tags":["image_datasets"],"summary":"Get Image Dataset","operationId":"image_datasets-get_image_dataset","security":[{"HTTPBearer":[]}],"parameters":[{"name":"dataset_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Dataset Id"}},{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DatasetResponse"}}}},"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":["image_datasets"],"summary":"Update Image Dataset","operationId":"image_datasets-update_image_dataset","security":[{"HTTPBearer":[]}],"parameters":[{"name":"dataset_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Dataset Id"}},{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DatasetUpdate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DatasetResponse"}}}},"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":["image_datasets"],"summary":"Delete Image Dataset","operationId":"image_datasets-delete_image_dataset","security":[{"HTTPBearer":[]}],"parameters":[{"name":"dataset_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Dataset 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}/datasets/images/{dataset_id}/stats":{"get":{"tags":["image_datasets"],"summary":"Get Image Dataset Stats","description":"Total + per-status counts of active assets — for the FE grid header\n(FE feedback 2026-05-07). Separate endpoint per ADR-0012 (cursor list,\nno totals on list responses).\n\nAlso reports embedding coverage (`embedding: {pending, ready, ...}`) so the\nFE can show a \"search-ready: 1872 / 2000\" badge and prompt for re-embed\nwhen new uploads land.","operationId":"image_datasets-get_image_dataset_stats","security":[{"HTTPBearer":[]}],"parameters":[{"name":"dataset_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Dataset Id"}},{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DatasetStatsResponse"}}}},"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}/datasets/images/{dataset_id}/embed":{"post":{"tags":["image_datasets"],"summary":"Embed Image Dataset","description":"Kick off a dataset-wide embedding pass (ADR-0033).\n\nEmbeds every active image asset in the dataset that doesn't yet have a\n`ready` embedding. Returns a job; poll `/jobs/{id}` for status. The\nactual GPU cost (gpu_seconds_used) is recorded on the job row when the\nSageMaker endpoint reports usage. Per-tenant 3-job cap (ADR-0023) applies.\n\nVideo datasets are not yet supported (returns 400). See ADR-0033 for\nthe rationale (single-vector vs per-segment scope decision deferred).","operationId":"image_datasets-embed_image_dataset","security":[{"HTTPBearer":[]}],"parameters":[{"name":"dataset_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Dataset Id"}},{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}},{"name":"Idempotency-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Idempotency-Key"}}],"responses":{"202":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/JobResponse"}}}},"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}/datasets/images/{dataset_id}/assets/upload-init":{"post":{"tags":["image_assets"],"summary":"Upload Init","operationId":"image_assets-upload_init","security":[{"HTTPBearer":[]}],"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}},{"name":"dataset_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Dataset Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AssetUploadInitRequest"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/AssetUploadInitSinglePartResponse"},{"$ref":"#/components/schemas/AssetUploadInitMultipartResponse"}],"discriminator":{"propertyName":"upload_type","mapping":{"single":"#/components/schemas/AssetUploadInitSinglePartResponse","multipart":"#/components/schemas/AssetUploadInitMultipartResponse"}},"title":"Response Image Assets-Upload Init"}}}},"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}/datasets/images/{dataset_id}/assets/upload-init/batch":{"post":{"tags":["image_assets"],"summary":"Upload Init Batch","description":"Fan-out of `/upload-init` for SDK / CLI ingest workflows: the caller passes up to 100 files, the server returns one presigned URL per file. Slashes the per-file RPC count by ~100×, which is the dominant cost when ingesting a folder of small images.\n\nPer-file failures (wrong MIME, oversized, etc.) are returned with `upload_type='error'` instead of failing the whole batch — the client maps responses back to inputs via `client_ref` and retries or skips individual items.\n\nFiles larger than the multipart threshold return `error_code='USE_PER_FILE_INIT'`; the caller falls back to the regular `/upload-init` for those (multipart needs per-part presigning that doesn't fit a batch shape).\n\n**`X-Upload-Session-Id` header (optional, ADR-0039)**: pass a UUID and the server uses it as the rollup correlation_id for every file in this call. SDK clients reuse one UUID across all chunks of one logical `upload()` invocation so the activity feed shows a single *\"uploaded N assets\"* row instead of one row per 100-file chunk. When omitted, the server mints a fresh UUID per call (current default; backwards compatible).","operationId":"image_assets-upload_init_batch","security":[{"HTTPBearer":[]}],"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}},{"name":"dataset_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Dataset Id"}},{"name":"X-Upload-Session-Id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Upload-Session-Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AssetUploadInitBatchRequest"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AssetUploadInitBatchResponse"}}}},"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}/datasets/images/{dataset_id}/assets/upload-init/{upload_id}/complete":{"post":{"tags":["image_assets"],"summary":"Upload Complete","operationId":"image_assets-upload_complete","security":[{"HTTPBearer":[]}],"parameters":[{"name":"upload_id","in":"path","required":true,"schema":{"type":"string","minLength":1,"title":"Upload Id"}},{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}},{"name":"dataset_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Dataset Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AssetUploadCompleteRequest"}}}},"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}/datasets/images/{dataset_id}/assets":{"get":{"tags":["image_assets"],"summary":"List Assets","operationId":"image_assets-list_assets","security":[{"HTTPBearer":[]}],"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}},{"name":"dataset_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Dataset Id"}},{"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":"uploaded_by","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"description":"Filter to assets uploaded by this user.","title":"Uploaded By"},"description":"Filter to assets uploaded by this user."},{"name":"created_at_from","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"description":"Inclusive lower bound (>=) on `created_at` (ISO-8601).","title":"Created At From"},"description":"Inclusive lower bound (>=) on `created_at` (ISO-8601)."},{"name":"created_at_to","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"description":"Exclusive upper bound (<) on `created_at` (ISO-8601).","title":"Created At To"},"description":"Exclusive upper bound (<) on `created_at` (ISO-8601)."},{"name":"status","in":"query","required":false,"schema":{"anyOf":[{"$ref":"#/components/schemas/AssetStatus"},{"type":"null"}],"description":"Filter on asset processing `status` (e.g. 'ready'). Exact match.","title":"Status"},"description":"Filter on asset processing `status` (e.g. 'ready'). Exact match."},{"name":"content_type","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter on MIME `content_type` (e.g. 'image/jpeg'). Exact match.","title":"Content Type"},"description":"Filter on MIME `content_type` (e.g. 'image/jpeg'). Exact match."},{"name":"embedding_status","in":"query","required":false,"schema":{"anyOf":[{"$ref":"#/components/schemas/EmbeddingStatus"},{"type":"null"}],"description":"Filter on `embedding_status` ('pending' / 'ready'). Use `pending` to list assets the next /embed pass will pick up.","title":"Embedding Status"},"description":"Filter on `embedding_status` ('pending' / 'ready'). Use `pending` to list assets the next /embed pass will pick up."},{"name":"include","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"CSV. `projects` → `[{id,name}]` per asset; `thumbnail_url` → presigned thumbnail URL per asset (saves the per-asset round trip in grid views).","title":"Include"},"description":"CSV. `projects` → `[{id,name}]` per asset; `thumbnail_url` → presigned thumbnail URL per asset (saves the per-asset round trip in grid views)."},{"name":"include_total","in":"query","required":false,"schema":{"type":"boolean","description":"When true, response carries `total` = COUNT(*) for the same filter (ignores cursor/limit). ~150ms extra at the 1M-row dataset cap; opt in only when the FE needs a 'showing X of Y' header.","default":false,"title":"Include Total"},"description":"When true, response carries `total` = COUNT(*) for the same filter (ignores cursor/limit). ~150ms extra at the 1M-row dataset cap; opt in only when the FE needs a 'showing X of Y' header."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AssetPage"}}}},"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}/datasets/images/{dataset_id}/assets/{asset_id}":{"get":{"tags":["image_assets"],"summary":"Get Asset","operationId":"image_assets-get_asset","security":[{"HTTPBearer":[]}],"parameters":[{"name":"asset_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Asset Id"}},{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}},{"name":"dataset_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Dataset Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AssetResponse"}}}},"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}/datasets/images/{dataset_id}/assets/{asset_id}/download-url":{"get":{"tags":["image_assets"],"summary":"Get Asset Download Url","description":"Presigned GET URL for the asset's S3 object. 1-hour expiry.\nUsed by the labeling app to render the image on canvas.","operationId":"image_assets-get_asset_download_url","security":[{"HTTPBearer":[]}],"parameters":[{"name":"asset_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Asset Id"}},{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}},{"name":"dataset_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Dataset Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AssetDownloadUrl"}}}},"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}/datasets/images/{dataset_id}/assets/{asset_id}/thumbnail-url":{"get":{"tags":["image_assets"],"summary":"Get Asset Thumbnail Url","description":"Presigned GET URL for the 320px-long-edge JPEG thumbnail (ADR-0026).\nReturns 404 if the thumbnail wasn't generated (extraction soft-failed, or\nthe asset was uploaded pre-feature). FE should fall back to /download-url\non 404.","operationId":"image_assets-get_asset_thumbnail_url","security":[{"HTTPBearer":[]}],"parameters":[{"name":"asset_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Asset Id"}},{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}},{"name":"dataset_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Dataset Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AssetDownloadUrl"}}}},"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}/datasets/images/{dataset_id}/assets/search":{"post":{"tags":["image_assets"],"summary":"Search Assets","description":"Multimodal similarity search over the dataset's embedded assets (ADR-0033).\n\nTop-K (default 50, max 200), no pagination. Filters AND the vector ranking\n— items that don't match the filters are excluded even if visually identical.\nOnly assets with `embedding_status = 'ready'` participate; unembedded assets\nare skipped.\n\n`query.parts` is an ordered list of {text|image_uri|asset_id} blocks; the\nmodel accepts mixed inputs in one query. `query.instruction` is optional\n(server picks a default per dataset kind; must be English for the\nretrieval-quality boost — ADR-0033).","operationId":"image_assets-search_assets","security":[{"HTTPBearer":[]}],"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}},{"name":"dataset_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Dataset Id"}},{"name":"include","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"CSV. `thumbnail_url` → presigned 320px-thumbnail GET per hit; `image_url` → full-res presigned GET per hit. Render the whole results grid in one round trip instead of a per-hit /thumbnail-url|/download-url fetch.","title":"Include"},"description":"CSV. `thumbnail_url` → presigned 320px-thumbnail GET per hit; `image_url` → full-res presigned GET per hit. Render the whole results grid in one round trip instead of a per-hit /thumbnail-url|/download-url fetch."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SearchRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SearchResponse"}}}},"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}/datasets/images/{dataset_id}/assets/bulk-delete":{"post":{"tags":["image_assets"],"summary":"Bulk Delete","operationId":"image_assets-bulk_delete","security":[{"HTTPBearer":[]}],"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}},{"name":"dataset_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Dataset Id"}},{"name":"Idempotency-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Idempotency-Key"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BulkOpRequest"}}}},"responses":{"202":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/JobResponse"}}}},"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}/datasets/images/{dataset_id}/assets/batch-delete":{"post":{"tags":["image_assets"],"summary":"Batch Delete Image Assets","description":"Sync counterpart to `/bulk-delete`. Selection-driven: `asset_ids` only. Cap `MAX_SYNC_BATCH_SIZE = 1000`. Hard-deletes via the central `hard_delete_assets` helper (ADR-0051); idempotent. ADR-0041.","operationId":"image_assets-batch_delete_image_assets","security":[{"HTTPBearer":[]}],"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}},{"name":"dataset_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Dataset Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AssetsBatchDeleteRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AssetsBatchDeleteResponse"}}}},"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}/jobs/{job_id}":{"get":{"tags":["jobs"],"summary":"Get Job","operationId":"jobs-get_job","security":[{"HTTPBearer":[]}],"parameters":[{"name":"job_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Job Id"}},{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/JobResponse"}}}},"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}/jobs":{"get":{"tags":["jobs"],"summary":"List Jobs","operationId":"jobs-list_jobs","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":"status","in":"query","required":false,"schema":{"anyOf":[{"type":"array","items":{"$ref":"#/components/schemas/JobStatus"}},{"type":"null"}],"description":"Filter to jobs in any of the given statuses.","title":"Status"},"description":"Filter to jobs in any of the given statuses."},{"name":"kind","in":"query","required":false,"schema":{"anyOf":[{"type":"array","items":{"$ref":"#/components/schemas/JobKind"}},{"type":"null"}],"description":"Filter to jobs of any of the given kinds.","title":"Kind"},"description":"Filter to jobs of any of the given kinds."},{"name":"created_by","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"description":"Filter to jobs created by this user id.","title":"Created By"},"description":"Filter to jobs created by this user id."},{"name":"dataset_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"description":"Filter to jobs whose target references this dataset id.","title":"Dataset Id"},"description":"Filter to jobs whose target references this dataset id."},{"name":"project_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"description":"Filter to jobs whose target references this project id.","title":"Project Id"},"description":"Filter to jobs whose target references this project id."},{"name":"created_at_from","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"description":"Inclusive lower bound (>=) on `created_at` (ISO-8601).","title":"Created At From"},"description":"Inclusive lower bound (>=) on `created_at` (ISO-8601)."},{"name":"created_at_to","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"description":"Exclusive upper bound (<) on `created_at` (ISO-8601).","title":"Created At To"},"description":"Exclusive upper bound (<) on `created_at` (ISO-8601)."},{"name":"include_total","in":"query","required":false,"schema":{"type":"boolean","description":"When true, response carries `total` = COUNT(*) for the same filter (ignores cursor/limit).","default":false,"title":"Include Total"},"description":"When true, response carries `total` = COUNT(*) for the same filter (ignores cursor/limit)."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/JobPage"}}}},"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}/versions":{"get":{"tags":["versions"],"summary":"List Tenant Versions","description":"Every version in the workspace, newest first. Each item carries `project_id`\nso the caller can attribute it to its owning project/dataset.","operationId":"versions-list_tenant_versions","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":"status","in":"query","required":false,"schema":{"anyOf":[{"$ref":"#/components/schemas/VersionStatus"},{"type":"null"}],"description":"`pending` | `building` | `ready` | `failed`. Filter by manifest build state.","title":"Status"},"description":"`pending` | `building` | `ready` | `failed`. Filter by manifest build state."},{"name":"created_at_from","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"description":"Inclusive lower bound (>=) on `created_at` (ISO-8601).","title":"Created At From"},"description":"Inclusive lower bound (>=) on `created_at` (ISO-8601)."},{"name":"created_at_to","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"description":"Exclusive upper bound (<) on `created_at` (ISO-8601).","title":"Created At To"},"description":"Exclusive upper bound (<) on `created_at` (ISO-8601)."},{"name":"include_total","in":"query","required":false,"schema":{"type":"boolean","description":"When true, response carries `total` = COUNT(*) for the same filter (ignores cursor/limit).","default":false,"title":"Include Total"},"description":"When true, response carries `total` = COUNT(*) for the same filter (ignores cursor/limit)."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/VersionPage"}}}},"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}/projects":{"post":{"tags":["projects"],"summary":"Create Project","operationId":"projects-create_project","security":[{"HTTPBearer":[]}],"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProjectCreate"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProjectResponse"}}}},"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":["projects"],"summary":"List Projects","operationId":"projects-list_projects","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":"dataset_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Dataset Id"}},{"name":"status","in":"query","required":false,"schema":{"anyOf":[{"$ref":"#/components/schemas/ProjectStatus"},{"type":"null"}],"title":"Status"}},{"name":"dataset_kind","in":"query","required":false,"schema":{"anyOf":[{"$ref":"#/components/schemas/DatasetKind"},{"type":"null"}],"description":"`image`. Filters to projects on that dataset kind.","title":"Dataset Kind"},"description":"`image`. Filters to projects on that dataset kind."},{"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":"created_at_from","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"description":"Inclusive lower bound (>=) on `created_at` (ISO-8601).","title":"Created At From"},"description":"Inclusive lower bound (>=) on `created_at` (ISO-8601)."},{"name":"created_at_to","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"description":"Exclusive upper bound (<) on `created_at` (ISO-8601).","title":"Created At To"},"description":"Exclusive upper bound (<) on `created_at` (ISO-8601)."},{"name":"include_total","in":"query","required":false,"schema":{"type":"boolean","description":"When true, response carries `total` = COUNT(*) for the same filter (ignores cursor/limit).","default":false,"title":"Include Total"},"description":"When true, response carries `total` = COUNT(*) for the same filter (ignores cursor/limit)."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProjectPage"}}}},"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}/projects/{project_id}":{"get":{"tags":["projects"],"summary":"Get Project Endpoint","operationId":"projects-get_project_endpoint","security":[{"HTTPBearer":[]}],"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}},{"name":"project_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Project Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProjectResponse"}}}},"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":["projects"],"summary":"Update Project","operationId":"projects-update_project","security":[{"HTTPBearer":[]}],"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}},{"name":"project_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Project Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProjectUpdate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProjectResponse"}}}},"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":["projects"],"summary":"Delete Project","description":"Permanently delete a project and everything scoped to it (ADR-0120).\n\nThis is a HARD delete — there is no archive/undo. It cascades away\nannotations, classes + attributes, members, versions (+ their frozen\nsnapshots), training runs, models, deployments, exports, tags, and jobs,\nand purges the project's S3 objects (export bundles, version manifests,\nlive-deployment model tarballs). Shared dataset images are NOT touched.\n\nThe delete is *async but invisible*: we synchronously flip the project to\nthe `deleting` tombstone (so it disappears from every read immediately —\n`get_project` already treats `deleting` as not-found), enqueue the cascade\njob, and return 204. The heavy DB cascade + S3 purge run in the background;\nthe user never sees a half-deleted project or waits on the API-GW timeout.","operationId":"projects-delete_project","security":[{"HTTPBearer":[]}],"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}},{"name":"project_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Project 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"}}}},"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}/projects/{project_id}/progress":{"get":{"tags":["projects"],"summary":"Get Project Progress","operationId":"projects-get_project_progress","security":[{"HTTPBearer":[]}],"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}},{"name":"project_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Project Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProjectProgress"}}}},"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}/projects/{project_id}/classes":{"get":{"tags":["projects"],"summary":"List Classes","operationId":"projects-list_classes","security":[{"HTTPBearer":[]}],"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}},{"name":"project_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Project Id"}},{"name":"include_archived","in":"query","required":false,"schema":{"type":"boolean","default":false,"title":"Include Archived"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ClassesResponse"}}}},"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"}}}}}},"post":{"tags":["projects"],"summary":"Create Class","description":"Create one top-level class — an object class (with optional inline attributes,\nfanned to owned rows in one txn) or an image-level classification (ADR-0090 §3).","operationId":"projects-create_class","security":[{"HTTPBearer":[]}],"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}},{"name":"project_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Project Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/ClassCreate"},{"$ref":"#/components/schemas/ImageClassificationCreate"}],"discriminator":{"propertyName":"kind","mapping":{"object":"#/components/schemas/ClassCreate","classification":"#/components/schemas/ImageClassificationCreate"}},"title":"Payload"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"anyOf":[{"$ref":"#/components/schemas/ClassResponse"},{"$ref":"#/components/schemas/ImageClassificationResponse"}],"title":"Response Projects-Create Class"}}}},"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}/projects/{project_id}/classes/{class_id}":{"patch":{"tags":["projects"],"summary":"Update Class","operationId":"projects-update_class","security":[{"HTTPBearer":[]}],"parameters":[{"name":"class_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Class Id"}},{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}},{"name":"project_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Project Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ClassUpdate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"anyOf":[{"$ref":"#/components/schemas/ClassResponse"},{"$ref":"#/components/schemas/ImageClassificationResponse"}],"title":"Response Projects-Update Class"}}}},"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":["projects"],"summary":"Delete Class","operationId":"projects-delete_class","security":[{"HTTPBearer":[]}],"parameters":[{"name":"class_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Class Id"}},{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}},{"name":"project_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Project 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"}}}},"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}/projects/{project_id}/classes/{class_id}/attributes":{"post":{"tags":["projects"],"summary":"Create Attribute","operationId":"projects-create_attribute","security":[{"HTTPBearer":[]}],"parameters":[{"name":"class_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Class Id"}},{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}},{"name":"project_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Project Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AttributeCreate"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AttributeResponse"}}}},"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}/projects/{project_id}/classes/{class_id}/attributes/{attribute_id}":{"patch":{"tags":["projects"],"summary":"Update Attribute","operationId":"projects-update_attribute","security":[{"HTTPBearer":[]}],"parameters":[{"name":"class_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Class Id"}},{"name":"attribute_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Attribute Id"}},{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}},{"name":"project_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Project Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AttributeUpdate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AttributeResponse"}}}},"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":["projects"],"summary":"Delete Attribute","operationId":"projects-delete_attribute","security":[{"HTTPBearer":[]}],"parameters":[{"name":"class_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Class Id"}},{"name":"attribute_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Attribute Id"}},{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}},{"name":"project_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Project 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"}}}},"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}/projects/{project_id}/members":{"post":{"tags":["projects"],"summary":"Add Member","operationId":"projects-add_member","security":[{"HTTPBearer":[]}],"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}},{"name":"project_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Project Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MemberAdd"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MemberResponse"}}}},"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":["projects"],"summary":"List Members","operationId":"projects-list_members","security":[{"HTTPBearer":[]}],"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}},{"name":"project_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Project Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MemberPage"}}}},"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}/projects/{project_id}/members/{user_id}":{"patch":{"tags":["projects"],"summary":"Update Member","operationId":"projects-update_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"}},{"name":"project_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Project Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MemberUpdate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MemberResponse"}}}},"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":["projects"],"summary":"Remove Member","operationId":"projects-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"}},{"name":"project_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Project 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"}}}},"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}/projects/{project_id}/versions":{"post":{"tags":["projects"],"summary":"Create Version","description":"Freeze the project's current state into an immutable version.\n\nSnapshots: in-scope asset_ids (filtered to non-deleted), full class_set,\nand kicks off a job that writes per-asset annotation manifests to S3.","operationId":"projects-create_version","security":[{"HTTPBearer":[]}],"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}},{"name":"project_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Project Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/VersionCreate"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/VersionResponse"}}}},"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":["projects"],"summary":"List Versions","operationId":"projects-list_versions","security":[{"HTTPBearer":[]}],"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}},{"name":"project_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Project Id"}},{"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":"status","in":"query","required":false,"schema":{"anyOf":[{"$ref":"#/components/schemas/VersionStatus"},{"type":"null"}],"description":"`pending` | `building` | `ready` | `failed`. Filter by manifest build state.","title":"Status"},"description":"`pending` | `building` | `ready` | `failed`. Filter by manifest build state."},{"name":"created_at_from","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"description":"Inclusive lower bound (>=) on `created_at` (ISO-8601).","title":"Created At From"},"description":"Inclusive lower bound (>=) on `created_at` (ISO-8601)."},{"name":"created_at_to","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"description":"Exclusive upper bound (<) on `created_at` (ISO-8601).","title":"Created At To"},"description":"Exclusive upper bound (<) on `created_at` (ISO-8601)."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/VersionPage"}}}},"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}/projects/{project_id}/versions/{version_id}":{"get":{"tags":["projects"],"summary":"Get Version","operationId":"projects-get_version","security":[{"HTTPBearer":[]}],"parameters":[{"name":"version_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Version Id"}},{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}},{"name":"project_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Project Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/VersionResponse"}}}},"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":["projects"],"summary":"Update Version","description":"Edit a version (ADR-0072) — name and/or description (partial). Name is unique\nper project (case-insensitive). Manager+ (admin inherits). Does NOT touch the\nfrozen snapshot — name/description are display metadata; the frozen\nasset_ids/class_set/manifest are unchanged. 409 on name collision.","operationId":"projects-update_version","security":[{"HTTPBearer":[]}],"parameters":[{"name":"version_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Version Id"}},{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}},{"name":"project_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Project Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/VersionUpdate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/VersionResponse"}}}},"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":["projects"],"summary":"Delete Version","description":"Hard-delete a version (ADR-0065). No soft-delete/archive — the FE guards\nagainst accidents with a type-the-name confirmation. The DB row is dropped\nimmediately (cascading its training runs/models); the version's S3 bytes (the\nmanifests + any cascaded models' weights) are cleaned up by a **silent,\ninternal** `cleanup_s3_objects` job (filtered out of `/jobs`, exactly like\nasset-delete byte GC). Any assets this version was the last to pin are\nre-evaluated by the reference-counted asset-purge sweep on its next pass.","operationId":"projects-delete_version","security":[{"HTTPBearer":[]}],"parameters":[{"name":"version_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Version Id"}},{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}},{"name":"project_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Project 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"}}}},"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}/projects/{project_id}/versions/{version_id}/assets":{"get":{"tags":["projects"],"summary":"List Version Assets","description":"List a version's **frozen** assets — the durable snapshot, not live rows\n(ADR-0065 Phase 3). Backed by `version_asset_index`, so it survives asset\ndeletion: an asset removed from the dataset since freeze still appears here,\nmarked **`version_only`**, with a working presigned URL to its retained bytes\n(vs **`present`** — still in the live dataset). Cursor-paginated over the\nstable frozen scope order. **All filters operate on the frozen snapshot**, not\nlive rows: `?q=` matches the frozen filename, `?tags=` the frozen tags (contains-ALL),\n`?classes=` the frozen annotations (ANY-of, ADR-0100), `?annotated=` whether the\nfrozen asset carries any annotation — plus the negative twins `?tags_none=`,\n`?classes_none=`, `?tagged=` (ADR-0115; `status_not`/`assigned` don't exist here —\nworkflow state isn't frozen). `total` is the count matching the filter. Each item carries frozen\nfilename/dims/tags + thumbnail + full image URL — everything the grid/list\nneeds without a live-row or manifest read.\n\nRead-only by nature; near-1:1 with the live asset list except editing and\nembedding/semantic search (a version_only asset has no embedding — the vector\nis CASCADE-dropped with the asset, and versions don't freeze vectors).","operationId":"projects-list_version_assets","security":[{"HTTPBearer":[]}],"parameters":[{"name":"version_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Version Id"}},{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}},{"name":"project_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Project Id"}},{"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":"tags","in":"query","required":false,"schema":{"anyOf":[{"type":"array","items":{"type":"string"}},{"type":"null"}],"description":"Repeatable `?tags=a&tags=b`. Contains-ALL: matches rows whose tag set includes every listed value (ADR-0050).","title":"Tags"},"description":"Repeatable `?tags=a&tags=b`. Contains-ALL: matches rows whose tag set includes every listed value (ADR-0050)."},{"name":"tags_none","in":"query","required":false,"schema":{"anyOf":[{"type":"array","items":{"type":"string"}},{"type":"null"}],"description":"Repeatable `?tags_none=a&tags_none=b`. Excludes rows carrying ANY listed tag (untagged rows match). Combine with `tags` for “all of these, except those” (ADR-0115). A value repeated in `tags` → 400.","title":"Tags None"},"description":"Repeatable `?tags_none=a&tags_none=b`. Excludes rows carrying ANY listed tag (untagged rows match). Combine with `tags` for “all of these, except those” (ADR-0115). A value repeated in `tags` → 400."},{"name":"tagged","in":"query","required":false,"schema":{"anyOf":[{"type":"boolean"},{"type":"null"}],"description":"`true`: rows with at least one tag; `false`: untagged rows. Contradictory with `tags` when false (400).","title":"Tagged"},"description":"`true`: rows with at least one tag; `false`: untagged rows. Contradictory with `tags` when false (400)."},{"name":"classes","in":"query","required":false,"schema":{"anyOf":[{"type":"array","items":{"type":"string","format":"uuid"}},{"type":"null"}],"description":"Repeatable `?classes=a&classes=b`. ANY-of: matches assets carrying at least one annotation of any listed class (ADR-0100). Unknown class ids match nothing.","title":"Classes"},"description":"Repeatable `?classes=a&classes=b`. ANY-of: matches assets carrying at least one annotation of any listed class (ADR-0100). Unknown class ids match nothing."},{"name":"classes_none","in":"query","required":false,"schema":{"anyOf":[{"type":"array","items":{"type":"string","format":"uuid"}},{"type":"null"}],"description":"Repeatable `?classes_none=a&classes_none=b`. Excludes assets carrying at least one annotation of any listed class (ADR-0115). Unknown class ids exclude nothing. A value repeated in `classes` → 400.","title":"Classes None"},"description":"Repeatable `?classes_none=a&classes_none=b`. Excludes assets carrying at least one annotation of any listed class (ADR-0115). Unknown class ids exclude nothing. A value repeated in `classes` → 400."},{"name":"annotated","in":"query","required":false,"schema":{"anyOf":[{"type":"boolean"},{"type":"null"}],"description":"`true`: assets with at least one annotation; `false`: assets with none (the to-label blanks). Contradictory with `classes` when false (400).","title":"Annotated"},"description":"`true`: assets with at least one annotation; `false`: assets with none (the to-label blanks). Contradictory with `classes` when false (400)."},{"name":"source","in":"query","required":false,"schema":{"anyOf":[{"type":"array","items":{"$ref":"#/components/schemas/AnnotationSource"}},{"type":"null"}],"description":"Repeatable `?source=manual&source=model`. ANY-of: matches assets carrying at least one annotation whose source is any listed provenance (`manual`/`imported`/`model`).","title":"Source"},"description":"Repeatable `?source=manual&source=model`. ANY-of: matches assets carrying at least one annotation whose source is any listed provenance (`manual`/`imported`/`model`)."},{"name":"source_none","in":"query","required":false,"schema":{"anyOf":[{"type":"array","items":{"$ref":"#/components/schemas/AnnotationSource"}},{"type":"null"}],"description":"Repeatable `?source_none=a&source_none=b`. Excludes assets carrying at least one annotation whose source is any listed provenance (ADR-0115). A value repeated in `source` → 400.","title":"Source None"},"description":"Repeatable `?source_none=a&source_none=b`. Excludes assets carrying at least one annotation whose source is any listed provenance (ADR-0115). A value repeated in `source` → 400."},{"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."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/VersionAssetPage"}}}},"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}/projects/{project_id}/versions/{version_id}/assets/{asset_id}":{"get":{"tags":["projects"],"summary":"Get Version Asset","description":"A single **frozen** asset + its frozen annotation shapes (ADR-0065 Phase 3),\nread straight from the version manifest — the durable source of the geometry —\nso it resolves even for a `version_only` asset whose live row (and live\n`…/assets/{id}/download-url`) is gone. The annotation canvas + deeplinks use\nthis to render a frozen asset, including deleted-but-pinned ones.","operationId":"projects-get_version_asset","security":[{"HTTPBearer":[]}],"parameters":[{"name":"version_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Version Id"}},{"name":"asset_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Asset Id"}},{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}},{"name":"project_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Project Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/VersionAssetDetail"}}}},"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}/projects/{project_id}/duplicate":{"post":{"tags":["projects"],"summary":"Duplicate Project","description":"Duplicate a project as a CONFIGURATION + data-selection clone (ADR-0122).\n\nCopies everything that defines *how the project is set up*: classes +\nattributes, members + roles, tag policy, auto-label config, guidelines\n(+ their S3 image attachments), and the asset **scope** (which images are in\nthe project). It copies **no work** — annotations, labeling status,\nassignments, reviewers, and per-asset tags all reset — and **no history** —\nversions, models, deployments, exports, comments, activity are never copied.\nA clone is a fresh labeling pass on the same setup + images.\n\nScope copy is set-based (`INSERT ... SELECT`). At/under\n`MAX_SYNC_DUPLICATE_SCOPE` it runs inline; above it, the clone + all config\nare created immediately and the (large) scope populates via an async job\n(kickoff/completion activity + progress), so the request never nears API GW's\n29s cap. Either way the new project is returned at once, fully configured.","operationId":"projects-duplicate_project","security":[{"HTTPBearer":[]}],"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}},{"name":"project_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Project Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProjectDuplicateRequest"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProjectResponse"}}}},"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}/projects/{project_id}/assets/bulk-update-tags":{"post":{"tags":["projects"],"summary":"Project Bulk Update Tags","description":"Combined async tag update — applies `tags_add` and/or `tags_remove`\natomically per worker batch. FE typically calls this when N > 1000 or\nthe selection is filter-driven; for ≤1000 explicit ids, use the sync\n`batch-update-tags` mirror.","operationId":"projects-project_bulk_update_tags","security":[{"HTTPBearer":[]}],"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}},{"name":"project_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Project Id"}},{"name":"Idempotency-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Idempotency-Key"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProjectBulkUpdateTagsRequest"}}}},"responses":{"202":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/JobResponse"}}}},"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}/projects/{project_id}/assets/batch-update-tags":{"post":{"tags":["projects"],"summary":"Project Assets Batch Update","description":"Sync counterpart to `/assets/bulk-update-tags`. Selection-driven: `asset_ids` only, capped at `MAX_SYNC_BATCH_SIZE = 1000`. Above the cap, fall through to the async endpoint. Returns inline per-id status; idempotent. Combined arms apply atomically (one UPDATE).","operationId":"projects-project_assets_batch_update","security":[{"HTTPBearer":[]}],"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}},{"name":"project_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Project Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProjectAssetsBatchUpdateRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProjectAssetsBatchUpdateResponse"}}}},"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}/projects/{project_id}/tags":{"get":{"tags":["projects"],"summary":"List Project Tags","description":"The project's authoritative tag palette (ADR-0058): every defined tag with\nits color/display + how many assets carry it (0 = defined but unused). The\npalette is the source of truth, so a defined-but-unused tag still appears.","operationId":"projects-list_project_tags","security":[{"HTTPBearer":[]}],"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}},{"name":"project_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Project Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProjectTagsResponse"}}}},"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"}}}}}},"post":{"tags":["projects"],"summary":"Create Project Tag","description":"Define a new tag in the project palette (ADR-0058). Manager+. 409 if the\nvalue already exists. A freshly defined tag has count 0 until it's applied.","operationId":"projects-create_project_tag","security":[{"HTTPBearer":[]}],"parameters":[{"name":"project_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Project Id"}},{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProjectTagCreateRequest"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProjectTagListItem"}}}},"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":["projects"],"summary":"Update Project Tag","description":"Rename and/or recolor/redisplay a palette tag (ADR-0058). Manager+.\n\nRename (`new_value`) rewrites every asset's tag array — `array_replace` +\ndedup, so a row already carrying `new_value` ends up with it once (a merge).\nRecolor/redisplay is a palette-only update. 404 if `value` isn't defined.","operationId":"projects-update_project_tag","security":[{"HTTPBearer":[]}],"parameters":[{"name":"project_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Project Id"}},{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProjectTagUpdateRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProjectTagUpdateResponse"}}}},"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":["projects"],"summary":"Delete Project Tag","description":"Delete a tag from the project: remove it from every asset that carries it,\nand drop it from the `tag_config` palette. Manager+.\n\nThe untag is a single GIN-indexed `array_remove` UPDATE (cost ∝ assets with\nthe tag — sub-second for realistic sizes; a 30s statement_timeout guards the\npathological case). Idempotent: deleting an unused tag returns\n`assets_untagged=0`. The tag then vanishes from `GET /tags` automatically\n(that list is derived from live asset tags, not stored separately).","operationId":"projects-delete_project_tag","security":[{"HTTPBearer":[]}],"parameters":[{"name":"project_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Project Id"}},{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}},{"name":"value","in":"query","required":true,"schema":{"type":"string","minLength":1,"maxLength":64,"description":"Tag value to delete project-wide (URL-encoded; tags may contain spaces/slashes).","title":"Value"},"description":"Tag value to delete project-wide (URL-encoded; tags may contain spaces/slashes)."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProjectTagDeleteResponse"}}}},"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}/projects/{project_id}/assets/tag-summary":{"post":{"tags":["projects"],"summary":"Project Tag Summary","description":"Aggregate tag application across a target set. Returns `total` (assets in scope after `exclude_asset_ids`) + per-tag `count`. Same `target` shape as `bulk-update-tags`. Read-only: any project member can call. Powers the unified bulk-tag editor (tri-state checkboxes vs the dual-arm add/remove pickers).","operationId":"projects-project_tag_summary","security":[{"HTTPBearer":[]}],"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}},{"name":"project_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Project Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProjectBulkOpRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProjectTagSummaryResponse"}}}},"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}/projects/{project_id}/assets/bulk-add":{"post":{"tags":["projects"],"summary":"Project Bulk Add Assets","description":"Add assets to this project's scope. Selection is dataset-scoped\n(`AssetFilter` — filename / created_* / uploaded_by). Async — returns\na Job. Idempotent on (project, asset) collisions.","operationId":"projects-project_bulk_add_assets","security":[{"HTTPBearer":[]}],"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}},{"name":"project_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Project Id"}},{"name":"Idempotency-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Idempotency-Key"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProjectBulkAddAssetsRequest"}}}},"responses":{"202":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/JobResponse"}}}},"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}/projects/{project_id}/assets/bulk-remove":{"post":{"tags":["projects"],"summary":"Project Bulk Remove Assets","description":"Remove assets from this project's scope. Selection is project-scoped\n(`ProjectFilter`). The worker cascade-wipes per-(project, asset) state\nacross annotations / comments / labeling_time_buckets in one\ntransaction. Idempotent.","operationId":"projects-project_bulk_remove_assets","security":[{"HTTPBearer":[]}],"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}},{"name":"project_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Project Id"}},{"name":"Idempotency-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Idempotency-Key"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProjectBulkRemoveAssetsRequest"}}}},"responses":{"202":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/JobResponse"}}}},"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}/projects/{project_id}/assets/batch-add":{"post":{"tags":["projects"],"summary":"Project Assets Batch Add","description":"Sync add up to MAX_SYNC_BATCH_SIZE asset_ids to this project. Idempotent — already-in-scope ids return as `skipped`. Above the cap, use /assets/bulk-add (async).","operationId":"projects-project_assets_batch_add","security":[{"HTTPBearer":[]}],"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}},{"name":"project_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Project Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProjectAssetsBatchAddRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProjectAssetsBatchAddResponse"}}}},"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}/projects/{project_id}/assets/batch-remove":{"post":{"tags":["projects"],"summary":"Project Assets Batch Remove","description":"Sync remove up to MAX_SYNC_BATCH_SIZE asset_ids from this project. Cascade-wipes annotations / comments / labeling_time_buckets for the matched (project, asset) pairs in one transaction. Idempotent — not-in-project ids return as `skipped`. Above the cap, use /assets/bulk-remove (async).","operationId":"projects-project_assets_batch_remove","security":[{"HTTPBearer":[]}],"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}},{"name":"project_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Project Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProjectAssetsBatchRemoveRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProjectAssetsBatchRemoveResponse"}}}},"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}/projects/{project_id}/assets/bulk-delete-annotations":{"post":{"tags":["projects"],"summary":"Project Bulk Delete Annotations","description":"Delete the annotations of the selected assets (ADR-0103), optionally\nnarrowed by `class_ids` / `source`. Async — returns a Job; `total` counts\nmatched non-locked annotations. Locked shapes are never deleted (reported\nas `skipped_locked` in the job's result_summary). Manager+.","operationId":"projects-project_bulk_delete_annotations","security":[{"HTTPBearer":[]}],"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}},{"name":"project_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Project Id"}},{"name":"Idempotency-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Idempotency-Key"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProjectBulkDeleteAnnotationsRequest"}}}},"responses":{"202":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/JobResponse"}}}},"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}/projects/{project_id}/assets/batch-delete-annotations":{"post":{"tags":["projects"],"summary":"Project Batch Delete Annotations","description":"Sync counterpart to `/assets/bulk-delete-annotations`. Selection-driven: `asset_ids` only, cap MAX_SYNC_BATCH_SIZE = 1000 — on BOTH the asset list and the matched-annotation count (the sync perf budget bounds deleted rows; above it, 413 directs to the bulk route). Locked shapes are skipped and reported. Idempotent. ADR-0103.","operationId":"projects-project_batch_delete_annotations","security":[{"HTTPBearer":[]}],"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}},{"name":"project_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Project Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProjectBatchDeleteAnnotationsRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProjectBatchDeleteAnnotationsResponse"}}}},"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}/projects/{project_id}/assets/bulk-assign":{"post":{"tags":["projects"],"summary":"Project Bulk Assign","description":"Async: assign every matching asset to `user_id`. To-label rows flip\nto in_progress (matches the claim-like semantics of single-asset\n/assign). Rows already assigned to `user_id` are silently skipped.","operationId":"projects-project_bulk_assign","security":[{"HTTPBearer":[]}],"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}},{"name":"project_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Project Id"}},{"name":"Idempotency-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Idempotency-Key"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProjectBulkAssignAssetsRequest"}}}},"responses":{"202":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/JobResponse"}}}},"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}/projects/{project_id}/assets/batch-assign":{"post":{"tags":["projects"],"summary":"Project Batch Assign","description":"Sync counterpart to bulk-assign. Cap MAX_SYNC_BATCH_SIZE.","operationId":"projects-project_batch_assign","security":[{"HTTPBearer":[]}],"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}},{"name":"project_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Project Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProjectAssetsBatchAssignRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProjectAssetsBatchAssignResponse"}}}},"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}/projects/{project_id}/assets/bulk-unassign":{"post":{"tags":["projects"],"summary":"Project Bulk Unassign","description":"Async: clear assignee on every matching asset. in_progress rows\nrevert to to_label. Already-unassigned rows are silently skipped.","operationId":"projects-project_bulk_unassign","security":[{"HTTPBearer":[]}],"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}},{"name":"project_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Project Id"}},{"name":"Idempotency-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Idempotency-Key"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProjectBulkUnassignAssetsRequest"}}}},"responses":{"202":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/JobResponse"}}}},"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}/projects/{project_id}/assets/batch-unassign":{"post":{"tags":["projects"],"summary":"Project Batch Unassign","operationId":"projects-project_batch_unassign","security":[{"HTTPBearer":[]}],"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}},{"name":"project_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Project Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProjectAssetsBatchUnassignRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProjectAssetsBatchUnassignResponse"}}}},"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}/projects/{project_id}/assets/bulk-approve":{"post":{"tags":["projects"],"summary":"Project Bulk Approve","description":"Async: approve every matching in_review asset. reviewer_id is the\njob creator. Rows in any other status are silently skipped.","operationId":"projects-project_bulk_approve","security":[{"HTTPBearer":[]}],"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}},{"name":"project_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Project Id"}},{"name":"Idempotency-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Idempotency-Key"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProjectBulkApproveAssetsRequest"}}}},"responses":{"202":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/JobResponse"}}}},"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}/projects/{project_id}/assets/batch-approve":{"post":{"tags":["projects"],"summary":"Project Batch Approve","operationId":"projects-project_batch_approve","security":[{"HTTPBearer":[]}],"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}},{"name":"project_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Project Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProjectAssetsBatchApproveRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProjectAssetsBatchApproveResponse"}}}},"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}/projects/{project_id}/assets/bulk-reject":{"post":{"tags":["projects"],"summary":"Project Bulk Reject","description":"Async: reject every matching in_review asset. rework_count++; if\n`reason` is provided, it lands on the state row AND a new Comment row\n(one per affected asset) so the labeler sees it in the unified thread.\nRows not in_review are silently skipped.","operationId":"projects-project_bulk_reject","security":[{"HTTPBearer":[]}],"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}},{"name":"project_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Project Id"}},{"name":"Idempotency-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Idempotency-Key"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProjectBulkRejectAssetsRequest"}}}},"responses":{"202":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/JobResponse"}}}},"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}/projects/{project_id}/assets/batch-reject":{"post":{"tags":["projects"],"summary":"Project Batch Reject","operationId":"projects-project_batch_reject","security":[{"HTTPBearer":[]}],"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}},{"name":"project_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Project Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProjectAssetsBatchRejectRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProjectAssetsBatchRejectResponse"}}}},"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}/projects/{project_id}/assets/bulk-set-status":{"post":{"tags":["projects"],"summary":"Project Bulk Set Status","description":"Async: manager force-sets every matching asset's workflow status to\n`target_status`, regardless of current status (override — not a guarded\ntransition). Side effects per target follow the equivalent single-asset\nmove (to_label clears assignment/review + resets rework; in_review stamps\nsubmitted; done stamps reviewer=creator; skipped stamps skipped_at). Rows\nalready at the target are silently skipped. Annotations are never touched.\nManager/admin only.","operationId":"projects-project_bulk_set_status","security":[{"HTTPBearer":[]}],"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}},{"name":"project_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Project Id"}},{"name":"Idempotency-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Idempotency-Key"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProjectBulkSetStatusAssetsRequest"}}}},"responses":{"202":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/JobResponse"}}}},"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}/projects/{project_id}/assets/batch-set-status":{"post":{"tags":["projects"],"summary":"Project Batch Set Status","description":"Sync counterpart to bulk-set-status. Cap MAX_SYNC_BATCH_SIZE. Rows already\nat `target_status` return as `skipped`. Manager/admin only.","operationId":"projects-project_batch_set_status","security":[{"HTTPBearer":[]}],"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}},{"name":"project_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Project Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProjectAssetsBatchSetStatusRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProjectAssetsBatchSetStatusResponse"}}}},"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}/projects/{project_id}/assets/{asset_id}/claim":{"post":{"tags":["projects-workflow"],"summary":"Claim Asset","description":"Labeler claims a to_label task for themselves.\n\nAllowed when the project's `assignment_mode='self_claim'` AND the task is\nnot already assigned to someone else. In `manual` mode, only the assigned\nlabeler can claim — and they get there via /assign first.","operationId":"projects-workflow-claim_asset","security":[{"HTTPBearer":[]}],"parameters":[{"name":"asset_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Asset Id"}},{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}},{"name":"project_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Project Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AssetStateResponse"}}}},"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}/projects/{project_id}/assets/{asset_id}/assign":{"post":{"tags":["projects-workflow"],"summary":"Assign Asset","description":"Manager assigns a task to a specific user. The user must be a project\nmember with role labeler/reviewer/manager (anyone who can label).","operationId":"projects-workflow-assign_asset","security":[{"HTTPBearer":[]}],"parameters":[{"name":"asset_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Asset Id"}},{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}},{"name":"project_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Project Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AssignRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AssetStateResponse"}}}},"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}/projects/{project_id}/assets/{asset_id}/unassign":{"post":{"tags":["projects-workflow"],"summary":"Unassign Asset","description":"Manager clears the current assignee. If the row is `in_progress`,\nstatus reverts to `to_label`. No-op (skipped) when already unassigned.","operationId":"projects-workflow-unassign_asset","security":[{"HTTPBearer":[]}],"parameters":[{"name":"asset_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Asset Id"}},{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}},{"name":"project_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Project Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AssetStateResponse"}}}},"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}/projects/{project_id}/assets/{asset_id}/submit":{"post":{"tags":["projects-workflow"],"summary":"Submit Asset","description":"Labeler submits their work for review. Must be the assigned labeler.","operationId":"projects-workflow-submit_asset","security":[{"HTTPBearer":[]}],"parameters":[{"name":"asset_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Asset Id"}},{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}},{"name":"project_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Project Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AssetStateResponse"}}}},"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}/projects/{project_id}/assets/{asset_id}/approve":{"post":{"tags":["projects-workflow"],"summary":"Approve Asset","operationId":"projects-workflow-approve_asset","security":[{"HTTPBearer":[]}],"parameters":[{"name":"asset_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Asset Id"}},{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}},{"name":"project_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Project Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AssetStateResponse"}}}},"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}/projects/{project_id}/assets/{asset_id}/reject":{"post":{"tags":["projects-workflow"],"summary":"Reject Asset","description":"Reviewer rejects work — auto-routes back to the original labeler.","operationId":"projects-workflow-reject_asset","security":[{"HTTPBearer":[]}],"parameters":[{"name":"asset_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Asset Id"}},{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}},{"name":"project_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Project Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RejectRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AssetStateResponse"}}}},"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}/projects/{project_id}/assets/{asset_id}/skip":{"post":{"tags":["projects-workflow"],"summary":"Skip Asset","description":"Labeler sets the asset aside — 'nothing to label' or unusable data (ADR-0110).\nTerminal `skipped` state, distinct from a done label. No reason captured (the\nskip guide is looser; labeler judgment is trusted). Waives required\nclassifications — skip means there's nothing to answer, including no scene.","operationId":"projects-workflow-skip_asset","security":[{"HTTPBearer":[]}],"parameters":[{"name":"asset_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Asset Id"}},{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}},{"name":"project_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Project Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AssetStateResponse"}}}},"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}/projects/{project_id}/assets/{asset_id}/reopen":{"post":{"tags":["projects-workflow"],"summary":"Reopen Asset","description":"Reviewer/manager revives a skipped asset back into the queue (ADR-0110) —\ne.g. when data is scarce and a set-aside asset must be labeled after all.","operationId":"projects-workflow-reopen_asset","security":[{"HTTPBearer":[]}],"parameters":[{"name":"asset_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Asset Id"}},{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}},{"name":"project_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Project Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AssetStateResponse"}}}},"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}/projects/{project_id}/queue":{"get":{"tags":["projects-workflow"],"summary":"Get My Queue","description":"Labeler's queue: tasks they should work on now.\n\n- Their assigned in_progress / to_label tasks\n- In `self_claim` mode, also unassigned to_label tasks (claimable)\n\nOptional `?status=` and `?rework=` filters power the 4-card hero UX\n(All / New / Rework / Continue) per ADR-0029.","operationId":"projects-workflow-get_my_queue","security":[{"HTTPBearer":[]}],"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}},{"name":"project_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Project Id"}},{"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":"status","in":"query","required":false,"schema":{"anyOf":[{"enum":["to_label","in_progress"],"type":"string"},{"type":"null"}],"description":"Optional: `to_label` or `in_progress`. Maps the FE 4-card hero (per ADR-0029).","title":"Status"},"description":"Optional: `to_label` or `in_progress`. Maps the FE 4-card hero (per ADR-0029)."},{"name":"rework","in":"query","required":false,"schema":{"anyOf":[{"type":"boolean"},{"type":"null"}],"description":"Optional: `true` filters to `rework_count > 0`; `false` to `rework_count = 0`. Combine with `status=in_progress` for the Rework / Continue split.","title":"Rework"},"description":"Optional: `true` filters to `rework_count > 0`; `false` to `rework_count = 0`. Combine with `status=in_progress` for the Rework / Continue split."},{"name":"include","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"CSV: `thumbnail_url` to embed a presigned thumbnail URL on each row's `asset.thumbnail_url`. Unknown values → 400.","title":"Include"},"description":"CSV: `thumbnail_url` to embed a presigned thumbnail URL on each row's `asset.thumbnail_url`. Unknown values → 400."},{"name":"include_total","in":"query","required":false,"schema":{"type":"boolean","description":"When true, response carries `total` = COUNT(*) for the same filter (ignores cursor/limit). `/queue/stats` is the cheaper choice for the unfiltered hero counts.","default":false,"title":"Include Total"},"description":"When true, response carries `total` = COUNT(*) for the same filter (ignores cursor/limit). `/queue/stats` is the cheaper choice for the unfiltered hero counts."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AssetStatePage"}}}},"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}/projects/{project_id}/queue/stats":{"get":{"tags":["projects-workflow"],"summary":"Queue Stats","description":"Bucket counts for the labeler's 4-card hero (ADR-0029).\n\nScoped to the caller's queue (their assigned tasks + the unassigned pool\nwhen `assignment_mode=self_claim`). Same scoping rules as `/queue`.","operationId":"projects-workflow-queue_stats","security":[{"HTTPBearer":[]}],"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}},{"name":"project_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Project Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/QueueStatsResponse"}}}},"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}/projects/{project_id}/assets":{"get":{"tags":["projects-workflow"],"summary":"List Asset States","description":"Paginated list of asset states across the project (manager dashboard).\n\n`?q=` matches the asset filename; `?tags=` is contains-ALL over the\nper-(project,asset) tag set (ADR-0050); `?classes=` is ANY-of over the asset's\nlive annotations (ADR-0100). Each has a negative twin (`tags_none`,\n`classes_none`, `status_not`, plus the `assigned`/`tagged` tri-states) that\ncomposes by AND — \"all of these, except those\" (ADR-0115).","operationId":"projects-workflow-list_asset_states","security":[{"HTTPBearer":[]}],"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}},{"name":"project_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Project Id"}},{"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":"status","in":"query","required":false,"schema":{"anyOf":[{"$ref":"#/components/schemas/WorkflowStatus"},{"type":"null"}],"description":"Filter to a single `WorkflowStatus` (`to_label`, `in_progress`, `in_review`, `done`). Unknown values → 422.","title":"Status"},"description":"Filter to a single `WorkflowStatus` (`to_label`, `in_progress`, `in_review`, `done`). Unknown values → 422."},{"name":"status_not","in":"query","required":false,"schema":{"anyOf":[{"type":"array","items":{"$ref":"#/components/schemas/WorkflowStatus"}},{"type":"null"}],"description":"Repeatable `?status_not=done&status_not=skipped` — exclude the listed statuses (ADR-0115). Unknown values → 422; a value repeated in `status` → 400.","title":"Status Not"},"description":"Repeatable `?status_not=done&status_not=skipped` — exclude the listed statuses (ADR-0115). Unknown values → 422; a value repeated in `status` → 400."},{"name":"assigned_to","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"description":"Filter to tasks assigned to a specific labeler (manager dashboard).","title":"Assigned To"},"description":"Filter to tasks assigned to a specific labeler (manager dashboard)."},{"name":"assigned","in":"query","required":false,"schema":{"anyOf":[{"type":"boolean"},{"type":"null"}],"description":"`true`: tasks assigned to anyone; `false`: the unassigned pool (ADR-0115). Contradictory with `assigned_to` when false (400).","title":"Assigned"},"description":"`true`: tasks assigned to anyone; `false`: the unassigned pool (ADR-0115). Contradictory with `assigned_to` when false (400)."},{"name":"reviewer_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"description":"Filter to tasks last reviewed by a specific reviewer.","title":"Reviewer Id"},"description":"Filter to tasks last reviewed by a specific reviewer."},{"name":"rework_count_min","in":"query","required":false,"schema":{"anyOf":[{"type":"integer","minimum":1},{"type":"null"}],"description":"Filter to tasks with `rework_count >= N`. Useful for QA queues.","title":"Rework Count Min"},"description":"Filter to tasks with `rework_count >= N`. Useful for QA queues."},{"name":"tags","in":"query","required":false,"schema":{"anyOf":[{"type":"array","items":{"type":"string"}},{"type":"null"}],"description":"Repeatable `?tags=a&tags=b`. Contains-ALL: matches rows whose tag set includes every listed value (ADR-0050).","title":"Tags"},"description":"Repeatable `?tags=a&tags=b`. Contains-ALL: matches rows whose tag set includes every listed value (ADR-0050)."},{"name":"tags_none","in":"query","required":false,"schema":{"anyOf":[{"type":"array","items":{"type":"string"}},{"type":"null"}],"description":"Repeatable `?tags_none=a&tags_none=b`. Excludes rows carrying ANY listed tag (untagged rows match). Combine with `tags` for “all of these, except those” (ADR-0115). A value repeated in `tags` → 400.","title":"Tags None"},"description":"Repeatable `?tags_none=a&tags_none=b`. Excludes rows carrying ANY listed tag (untagged rows match). Combine with `tags` for “all of these, except those” (ADR-0115). A value repeated in `tags` → 400."},{"name":"tagged","in":"query","required":false,"schema":{"anyOf":[{"type":"boolean"},{"type":"null"}],"description":"`true`: rows with at least one tag; `false`: untagged rows. Contradictory with `tags` when false (400).","title":"Tagged"},"description":"`true`: rows with at least one tag; `false`: untagged rows. Contradictory with `tags` when false (400)."},{"name":"classes","in":"query","required":false,"schema":{"anyOf":[{"type":"array","items":{"type":"string","format":"uuid"}},{"type":"null"}],"description":"Repeatable `?classes=a&classes=b`. ANY-of: matches assets carrying at least one annotation of any listed class (ADR-0100). Unknown class ids match nothing.","title":"Classes"},"description":"Repeatable `?classes=a&classes=b`. ANY-of: matches assets carrying at least one annotation of any listed class (ADR-0100). Unknown class ids match nothing."},{"name":"classes_none","in":"query","required":false,"schema":{"anyOf":[{"type":"array","items":{"type":"string","format":"uuid"}},{"type":"null"}],"description":"Repeatable `?classes_none=a&classes_none=b`. Excludes assets carrying at least one annotation of any listed class (ADR-0115). Unknown class ids exclude nothing. A value repeated in `classes` → 400.","title":"Classes None"},"description":"Repeatable `?classes_none=a&classes_none=b`. Excludes assets carrying at least one annotation of any listed class (ADR-0115). Unknown class ids exclude nothing. A value repeated in `classes` → 400."},{"name":"annotated","in":"query","required":false,"schema":{"anyOf":[{"type":"boolean"},{"type":"null"}],"description":"`true`: assets with at least one annotation; `false`: assets with none (the to-label blanks). Contradictory with `classes` when false (400).","title":"Annotated"},"description":"`true`: assets with at least one annotation; `false`: assets with none (the to-label blanks). Contradictory with `classes` when false (400)."},{"name":"source","in":"query","required":false,"schema":{"anyOf":[{"type":"array","items":{"$ref":"#/components/schemas/AnnotationSource"}},{"type":"null"}],"description":"Repeatable `?source=manual&source=model`. ANY-of: matches assets carrying at least one annotation whose source is any listed provenance (`manual`/`imported`/`model`).","title":"Source"},"description":"Repeatable `?source=manual&source=model`. ANY-of: matches assets carrying at least one annotation whose source is any listed provenance (`manual`/`imported`/`model`)."},{"name":"source_none","in":"query","required":false,"schema":{"anyOf":[{"type":"array","items":{"$ref":"#/components/schemas/AnnotationSource"}},{"type":"null"}],"description":"Repeatable `?source_none=a&source_none=b`. Excludes assets carrying at least one annotation whose source is any listed provenance (ADR-0115). A value repeated in `source` → 400.","title":"Source None"},"description":"Repeatable `?source_none=a&source_none=b`. Excludes assets carrying at least one annotation whose source is any listed provenance (ADR-0115). A value repeated in `source` → 400."},{"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":"uploaded_by","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"description":"Filter to assets uploaded by a specific user.","title":"Uploaded By"},"description":"Filter to assets uploaded by a specific user."},{"name":"created_at_from","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"description":"Inclusive lower bound (>=) on `created_at` (ISO-8601).","title":"Created At From"},"description":"Inclusive lower bound (>=) on `created_at` (ISO-8601)."},{"name":"created_at_to","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"description":"Exclusive upper bound (<) on `created_at` (ISO-8601).","title":"Created At To"},"description":"Exclusive upper bound (<) on `created_at` (ISO-8601)."},{"name":"include","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"CSV of per-row summary embeds (ADR-0133): `thumbnail_url` (presigned GET on `asset.thumbnail_url`), `annotation_source_counts` (live counts `{manual, imported, model}`), `classifications` (answered image-level classifications). Unknown values → 400.","title":"Include"},"description":"CSV of per-row summary embeds (ADR-0133): `thumbnail_url` (presigned GET on `asset.thumbnail_url`), `annotation_source_counts` (live counts `{manual, imported, model}`), `classifications` (answered image-level classifications). Unknown values → 400."},{"name":"include_total","in":"query","required":false,"schema":{"type":"boolean","description":"When true, response carries `total` = COUNT(*) for the same filter (ignores cursor/limit).","default":false,"title":"Include Total"},"description":"When true, response carries `total` = COUNT(*) for the same filter (ignores cursor/limit)."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AssetStatePage"}}}},"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}/projects/{project_id}/assets/{asset_id}":{"get":{"tags":["projects-workflow"],"summary":"Get Labeling Context","description":"One-shot fetch for the labeler page: state + asset metadata + annotations\non this project + presigned download URL + project class_map. Saves the FE\n~4 round-trips per asset open.\n\nNote: caller must be a project member (any role); workspace admins are implicit\nproject managers per `require_project_member`.","operationId":"projects-workflow-get_labeling_context","security":[{"HTTPBearer":[]}],"parameters":[{"name":"asset_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Asset Id"}},{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}},{"name":"project_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Project Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LabelingContext"}}}},"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}/projects/{project_id}/assets/{asset_id}/annotations":{"post":{"tags":["projects-workflow"],"summary":"Create Annotation","operationId":"projects-workflow-create_annotation","security":[{"HTTPBearer":[]}],"parameters":[{"name":"asset_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Asset Id"}},{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}},{"name":"project_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Project Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AnnotationCreate"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AnnotationResponse"}}}},"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}/projects/{project_id}/assets/{asset_id}/annotations/batch-create":{"post":{"tags":["projects-workflow"],"summary":"Batch Create Annotations","description":"Create many annotations on one asset in a single call (ADR-0061) — the\nlabeling submit path. All-or-nothing: any invalid row → 422 with its index,\nnothing inserted. `source` is always `manual`. `replace=true` first clears\nthe asset's existing annotations in this project. Cap MAX_SYNC_BATCH_SIZE.","operationId":"projects-workflow-batch_create_annotations","security":[{"HTTPBearer":[]}],"parameters":[{"name":"asset_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Asset Id"}},{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}},{"name":"project_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Project Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AnnotationBatchCreate"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/AnnotationResponse"},"title":"Response Projects-Workflow-Batch Create Annotations"}}}},"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}/projects/{project_id}/annotations/{annotation_id}":{"patch":{"tags":["projects-workflow"],"summary":"Update Annotation","operationId":"projects-workflow-update_annotation","security":[{"HTTPBearer":[]}],"parameters":[{"name":"annotation_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Annotation Id"}},{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}},{"name":"project_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Project Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AnnotationUpdate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AnnotationResponse"}}}},"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":["projects-workflow"],"summary":"Delete Annotation","operationId":"projects-workflow-delete_annotation","security":[{"HTTPBearer":[]}],"parameters":[{"name":"annotation_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Annotation Id"}},{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}},{"name":"project_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Project 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"}}}},"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}/projects/{project_id}/annotations":{"get":{"tags":["annotations"],"summary":"List Annotations","description":"Annotation-level cursor-paginated list (ADR-0029 Phase 1).\n\nDefault sort: `created_at DESC, id DESC` — backed by `ix_ann_project_created`\n+ `ix_ann_project_class`. Empirically <1 ms at 5M rows.\n\nPhase 1 advertises NO `?sort=` parameter — only the default. Phase 2 will\nadd an allowlist + index when the FE asks.\n\nWhen `version_id` is set, the read swaps to the S3 manifest backing that\nproject version (see ADR-0024). The fan-out is per-asset, so the request\nmust specify `asset_id` or `asset_ids` — version-wide enumeration is left\nto a separate paginated endpoint (deferred until the FE asks for it).","operationId":"annotations-list_annotations","security":[{"HTTPBearer":[]}],"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}},{"name":"project_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Project Id"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":200,"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":"class_id","in":"query","required":false,"schema":{"anyOf":[{"type":"array","items":{"type":"string","format":"uuid"}},{"type":"null"}],"description":"Repeatable class filter: `?class_id=a&class_id=b` → `class_id IN (...)`.","title":"Class Id"},"description":"Repeatable class filter: `?class_id=a&class_id=b` → `class_id IN (...)`."},{"name":"type","in":"query","required":false,"schema":{"anyOf":[{"$ref":"#/components/schemas/AnnotationType"},{"type":"null"}],"description":"Annotation type (the geometry discriminator). Exact match; invalid values 422.","title":"Type"},"description":"Annotation type (the geometry discriminator). Exact match; invalid values 422."},{"name":"source","in":"query","required":false,"schema":{"anyOf":[{"type":"array","items":{"$ref":"#/components/schemas/AnnotationSource"}},{"type":"null"}],"description":"Repeatable `?source=manual&source=model`. ANY-of provenance filter (`manual`/`imported`/`model`); a single value keeps the prior exact-match behavior.","title":"Source"},"description":"Repeatable `?source=manual&source=model`. ANY-of provenance filter (`manual`/`imported`/`model`); a single value keeps the prior exact-match behavior."},{"name":"source_none","in":"query","required":false,"schema":{"anyOf":[{"type":"array","items":{"$ref":"#/components/schemas/AnnotationSource"}},{"type":"null"}],"description":"Repeatable `?source_none=a&source_none=b`. Excludes annotations whose source is any listed provenance (negative twin, ADR-0115). A value repeated in `source` → 400.","title":"Source None"},"description":"Repeatable `?source_none=a&source_none=b`. Excludes annotations whose source is any listed provenance (negative twin, ADR-0115). A value repeated in `source` → 400."},{"name":"created_by","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"description":"Filter to annotations authored by this user (labeler / model actor).","title":"Created By"},"description":"Filter to annotations authored by this user (labeler / model actor)."},{"name":"asset_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"description":"Filter to annotations on this single asset. Mutually exclusive with `asset_ids`.","title":"Asset Id"},"description":"Filter to annotations on this single asset. Mutually exclusive with `asset_ids`."},{"name":"asset_ids","in":"query","required":false,"schema":{"anyOf":[{"type":"array","items":{"type":"string","format":"uuid"}},{"type":"null"}],"description":"Repeatable `?asset_ids=a&asset_ids=b` → filter to annotations on any of these assets (max 100). Used by the FE grid view to merge per-asset annotation calls into one request. Mutually exclusive with `asset_id`.","title":"Asset Ids"},"description":"Repeatable `?asset_ids=a&asset_ids=b` → filter to annotations on any of these assets (max 100). Used by the FE grid view to merge per-asset annotation calls into one request. Mutually exclusive with `asset_id`."},{"name":"instance_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"description":"Filter to annotations sharing this instance id (multi-shape instance grouping).","title":"Instance Id"},"description":"Filter to annotations sharing this instance id (multi-shape instance grouping)."},{"name":"created_at_from","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"description":"Inclusive lower bound (>=) on `created_at` (ISO-8601).","title":"Created At From"},"description":"Inclusive lower bound (>=) on `created_at` (ISO-8601)."},{"name":"created_at_to","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"description":"Exclusive upper bound (<) on `created_at` (ISO-8601).","title":"Created At To"},"description":"Exclusive upper bound (<) on `created_at` (ISO-8601)."},{"name":"include","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"CSV of sub-resources to embed: `asset`, `class`. Whole-record only.","title":"Include"},"description":"CSV of sub-resources to embed: `asset`, `class`. Whole-record only."},{"name":"include_total","in":"query","required":false,"schema":{"type":"boolean","description":"When true, response carries `total` = COUNT(*) for the same filter (ignores cursor/limit). Sub-400ms at the 5M-row project cap; opt in only when the FE needs a 'showing X of Y' total.","default":false,"title":"Include Total"},"description":"When true, response carries `total` = COUNT(*) for the same filter (ignores cursor/limit). Sub-400ms at the 5M-row project cap; opt in only when the FE needs a 'showing X of Y' total."},{"name":"version_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"description":"When set, read annotations from this version's frozen S3 manifests instead of the live `annotation` table — same response shape, but the data is what was frozen at version-build time. Requires `asset_id` or `asset_ids` to scope the fan-out (capped at 100). Version must belong to this project (404 otherwise) and have `status=ready` (425 otherwise). All other filters (class_id, type, source, created_by, instance_id, created_at_*) are applied in memory after the manifest read. Sort + cursor pagination work the same way as the live path.","title":"Version Id"},"description":"When set, read annotations from this version's frozen S3 manifests instead of the live `annotation` table — same response shape, but the data is what was frozen at version-build time. Requires `asset_id` or `asset_ids` to scope the fan-out (capped at 100). Version must belong to this project (404 otherwise) and have `status=ready` (425 otherwise). All other filters (class_id, type, source, created_by, instance_id, created_at_*) are applied in memory after the manifest read. Sort + cursor pagination work the same way as the live path."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AnnotationListPage"}}}},"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}/projects/{project_id}/annotations/batch-delete":{"post":{"tags":["annotations"],"summary":"Batch Delete Annotations","description":"Delete up to 500 annotations by id, scoped to this project.\n\nPOST `batch-delete` (not DELETE-with-body — proxies strip request bodies on\nDELETE per RFC 9110 §9.3.5 / cloud-LB behavior). Hard-cap 500 to keep p95 under\n~200 ms (per ADR-0029 perf test); FE chunks for larger sets.","operationId":"annotations-batch_delete_annotations","security":[{"HTTPBearer":[]}],"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}},{"name":"project_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Project Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AnnotationBatchDeleteRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AnnotationBatchDeleteResponse"}}}},"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}/projects/{project_id}/annotations/batch-create":{"post":{"tags":["annotations"],"summary":"Project Batch Create Annotations","description":"Create a flat mapped list of annotations across many assets in one sync\ncall (ADR-0061) — the import/prediction path. Each row references its asset\nby `asset_id` or `filename`. All-or-nothing: any invalid row → 422 with its\nindex, nothing inserted. Cap MAX_SYNC_BATCH_SIZE. manager+.","operationId":"annotations-project_batch_create_annotations","security":[{"HTTPBearer":[]}],"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}},{"name":"project_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Project Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProjectAnnotationBatchCreate"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProjectAnnotationBatchCreateResponse"}}}},"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}/projects/{project_id}/annotations/import-init":{"post":{"tags":["annotations"],"summary":"Annotation Import Init","description":"Mint a presigned PUT for a staged NDJSON import file (ADR-0061). The\nfile's byte count is pinned at S3 (`size_bytes`), capped at\nMAX_IMPORT_FILE_BYTES (over → 422 here). PUT the raw NDJSON, then pass the\nreturned `import_id` to `bulk-create`. 15-min TTL. manager+, rate-limited.","operationId":"annotations-annotation_import_init","security":[{"HTTPBearer":[]}],"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}},{"name":"project_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Project Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AnnotationImportInitRequest"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AnnotationImportInitResponse"}}}},"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}/projects/{project_id}/annotations/bulk-create":{"post":{"tags":["annotations"],"summary":"Project Bulk Create Annotations","description":"Kick off an async annotation-create job (ADR-0061) — for large imports /\npredictions. Body is EITHER `annotations` inline (≤~6 MB) OR an `import_id`\nfrom `import-init` (staged NDJSON). The worker scans (count + size + parse),\nthen validates + inserts in resumable batches, reporting per-row errors.\nmanager+. Returns a Job (202).","operationId":"annotations-project_bulk_create_annotations","security":[{"HTTPBearer":[]}],"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}},{"name":"project_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Project Id"}},{"name":"Idempotency-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Idempotency-Key"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProjectAnnotationBulkCreate"}}}},"responses":{"202":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/JobResponse"}}}},"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}/projects/{project_id}/annotations/batch":{"post":{"tags":["annotations"],"summary":"Batch Edit Annotations","description":"Create, update, and delete annotations in ONE atomic transaction — the\neditor \"save\" path (ADR-0070). All-or-nothing: any validation or edit-lock\nfailure rolls back the whole batch and returns the offending items keyed by\n`id`. `deletes` is idempotent (ids already gone are no-ops). Applied order is\ncreate → update → delete; the response echoes each bucket's ids (ADR-0092).\n\nCaps: `MAX_SYNC_BATCH_SIZE` ops and `MAX_SYNC_BATCH_BYTES` body (the route\ndeps reject over-cap before parse). Edit-lock is enforced per touched asset\nvia the same stage-handoff matrix as single edit; manager/reviewer override.","operationId":"annotations-batch_edit_annotations","security":[{"HTTPBearer":[]}],"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}},{"name":"project_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Project Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AnnotationBatchEditRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AnnotationBatchEditResponse"}}}},"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}/projects/{project_id}/assets/{asset_id}/revisions":{"get":{"tags":["annotations"],"summary":"List Asset Revisions","description":"List an asset's commits (newest first). One entry per submit/approve/reject\nboundary, with who committed it and how many annotations it captured.","operationId":"annotations-list_asset_revisions","security":[{"HTTPBearer":[]}],"parameters":[{"name":"asset_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Asset Id"}},{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}},{"name":"project_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Project Id"}},{"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."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RevisionSummaryPage"}}}},"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}/projects/{project_id}/assets/{asset_id}/revisions/{seq}":{"get":{"tags":["annotations"],"summary":"Get Asset Revision","description":"The full annotation snapshot at one commit — what the editor renders to\nview or diff a past state.","operationId":"annotations-get_asset_revision","security":[{"HTTPBearer":[]}],"parameters":[{"name":"asset_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Asset Id"}},{"name":"seq","in":"path","required":true,"schema":{"type":"integer","minimum":1,"title":"Seq"}},{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}},{"name":"project_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Project Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RevisionDetail"}}}},"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}/projects/{project_id}/annotations/{annotation_id}/revisions":{"get":{"tags":["annotations"],"summary":"List Annotation Revisions","description":"One shape's state at each commit it appeared in (newest first) — the\nGitHub-blame timeline for a single annotation.","operationId":"annotations-list_annotation_revisions","security":[{"HTTPBearer":[]}],"parameters":[{"name":"annotation_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Annotation Id"}},{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}},{"name":"project_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Project Id"}},{"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."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AnnotationBlamePage"}}}},"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}/projects/{project_id}/assets/{asset_id}/comments":{"post":{"tags":["comments"],"summary":"Create Comment","description":"Create a root comment or a reply. Replies inherit the root's anchor +\nasset_id; only `body` (and `parent_id`) are required.","operationId":"comments-create_comment","security":[{"HTTPBearer":[]}],"parameters":[{"name":"asset_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Asset Id"}},{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}},{"name":"project_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Project Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CommentCreateRequest"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CommentResponse"}}}},"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":["comments"],"summary":"List Asset Comments","description":"Per-asset thread list. Cursor-paginated (created_at desc, id desc).\n\nDefault view returns root comments only (parent_id IS NULL). Pass\n`?parent_id=<root>` to load replies under a specific thread.","operationId":"comments-list_asset_comments","security":[{"HTTPBearer":[]}],"parameters":[{"name":"asset_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Asset Id"}},{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}},{"name":"project_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Project Id"}},{"name":"status","in":"query","required":false,"schema":{"anyOf":[{"$ref":"#/components/schemas/CommentStatus"},{"type":"null"}],"description":"Filter root threads by status. Replies are returned with their root.","title":"Status"},"description":"Filter root threads by status. Replies are returned with their root."},{"name":"parent_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"description":"When set, returns only replies under this root (flat list).","title":"Parent Id"},"description":"When set, returns only replies under this root (flat list)."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":200,"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":"include_total","in":"query","required":false,"schema":{"type":"boolean","description":"When true, response carries `total` = COUNT(*) for the same filter (ignores cursor/limit). ~8ms median at 100k comments/project.","default":false,"title":"Include Total"},"description":"When true, response carries `total` = COUNT(*) for the same filter (ignores cursor/limit). ~8ms median at 100k comments/project."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CommentListPage"}}}},"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}/projects/{project_id}/comments":{"get":{"tags":["comments"],"summary":"List Project Comments","description":"Project-wide comment feed (the \"issues\" view). Roots only by default —\nthreading is loaded per-asset, not in the project feed.","operationId":"comments-list_project_comments","security":[{"HTTPBearer":[]}],"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}},{"name":"project_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Project Id"}},{"name":"status","in":"query","required":false,"schema":{"anyOf":[{"$ref":"#/components/schemas/CommentStatus"},{"type":"null"}],"description":"Filter root threads by status. Invalid values 422.","title":"Status"},"description":"Filter root threads by status. Invalid values 422."},{"name":"author","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"description":"Filter to threads opened by this user (the comment's `author_id`).","title":"Author"},"description":"Filter to threads opened by this user (the comment's `author_id`)."},{"name":"asset_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"description":"Filter to threads anchored on this asset.","title":"Asset Id"},"description":"Filter to threads anchored on this asset."},{"name":"created_at_from","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"description":"Inclusive lower bound (>=) on `created_at` (ISO-8601).","title":"Created At From"},"description":"Inclusive lower bound (>=) on `created_at` (ISO-8601)."},{"name":"created_at_to","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"description":"Exclusive upper bound (<) on `created_at` (ISO-8601).","title":"Created At To"},"description":"Exclusive upper bound (<) on `created_at` (ISO-8601)."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":200,"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":"include_total","in":"query","required":false,"schema":{"type":"boolean","description":"When true, response carries `total` = COUNT(*) for the same filter (ignores cursor/limit).","default":false,"title":"Include Total"},"description":"When true, response carries `total` = COUNT(*) for the same filter (ignores cursor/limit)."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CommentListPage"}}}},"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}/projects/{project_id}/comments/{comment_id}":{"patch":{"tags":["comments"],"summary":"Update Comment","description":"Edit a comment's body and/or its anchor (drag-to-move). Author or project\nmanager only. Anchor edits apply to root comments only — a reply's anchor is\ninherited from its root and can't be moved independently.","operationId":"comments-update_comment","security":[{"HTTPBearer":[]}],"parameters":[{"name":"comment_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Comment Id"}},{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}},{"name":"project_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Project Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CommentUpdateRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CommentResponse"}}}},"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":["comments"],"summary":"Delete Comment","description":"Soft-delete. Replies are preserved (their parent_id still resolves).","operationId":"comments-delete_comment","security":[{"HTTPBearer":[]}],"parameters":[{"name":"comment_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Comment Id"}},{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}},{"name":"project_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Project 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"}}}},"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}/projects/{project_id}/comments/{comment_id}/resolve":{"post":{"tags":["comments"],"summary":"Resolve Comment","description":"Mark a root comment resolved. Replies don't carry status; resolving a\nreply 404s (use the root's id). Idempotent.","operationId":"comments-resolve_comment","security":[{"HTTPBearer":[]}],"parameters":[{"name":"comment_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Comment Id"}},{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}},{"name":"project_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Project Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CommentResponse"}}}},"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}/projects/{project_id}/comments/{comment_id}/reopen":{"post":{"tags":["comments"],"summary":"Reopen Comment","description":"Reopen a resolved root comment. Idempotent.","operationId":"comments-reopen_comment","security":[{"HTTPBearer":[]}],"parameters":[{"name":"comment_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Comment Id"}},{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}},{"name":"project_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Project Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CommentResponse"}}}},"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}/projects/{project_id}/guideline":{"get":{"tags":["project-guidelines"],"summary":"Read the project guideline (Markdown).","description":"Read the guideline. Any project member + workspace admin can read.\n\nImage URLs in the doc are refreshed to 1-hour presigned-GETs before\nreturn so the labeler's browser can render them without direct S3\naccess.","operationId":"project-guidelines-get_guideline","security":[{"HTTPBearer":[]}],"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}},{"name":"project_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Project Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProjectGuidelineResponse"}}}},"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"}}}}}},"put":{"tags":["project-guidelines"],"summary":"Upsert the project guideline. Project managers + workspace admin only.","description":"Upsert the guideline content. Manager-only.\n\nPydantic enforces the 1 MB cap at parse time → 422 over-size. The\nactivity feed records `project.guideline_updated` with char count\ndelta + image count (not the content itself — privacy + size).","operationId":"project-guidelines-upsert_guideline","security":[{"HTTPBearer":[]}],"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}},{"name":"project_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Project Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProjectGuidelineUpdate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProjectGuidelineResponse"}}}},"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}/projects/{project_id}/guideline/images/upload-init":{"post":{"tags":["project-guidelines"],"summary":"Mint a presigned PUT URL for a guideline image. Manager-only.","description":"Mint a presigned PUT for a guideline image.\n\nCaller PUTs the image bytes directly to S3 using `upload_url`, then\ndrops `public_url` into the guideline markdown as the `<img src>`.\n\n`public_url` is a 1-hour presigned GET. When labelers read the\nguideline later, `GET /guideline` regenerates these URLs to fresh\n1-hour windows — the FE never needs to handle expiry.","operationId":"project-guidelines-init_guideline_image_upload","security":[{"HTTPBearer":[]}],"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}},{"name":"project_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Project Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/GuidelineImageUploadInitRequest"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GuidelineImageUploadInitResponse"}}}},"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}/projects/{project_id}/labeling-time/heartbeat":{"post":{"tags":["labeling-time"],"summary":"Record active seconds for the calling user. Any project member.","description":"Fire one heartbeat from the FE timer. Server records the time\nagainst (caller, project, asset, class_id, today).\n\nADR-0046 §4 mitigations:\n #2 — `active_seconds` > 30 → 422 at pydantic validate (cap is in the schema).\n #3 — `heartbeat_id` deduped within a 5-min sliding window. Replays return 204 silently.\n #4 — Wall-clock ceiling is applied at REPORT time, not here.\n\n`user_id` comes from the JWT — labelers can only record their own time.","operationId":"labeling-time-post_heartbeat","security":[{"HTTPBearer":[]}],"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}},{"name":"project_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Project Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LabelingTimeHeartbeat"}}}},"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}/projects/{project_id}/labeling-time":{"get":{"tags":["labeling-time"],"summary":"Aggregated active-seconds report. group_by drives the row shape.","description":"Aggregated active-seconds. See ADR-0046 §2 for the wire shape.\n\nApply the wall-clock ceiling (Tier-1 mitigation #4) after the SQL\ngroup: for every (user, asset) pair in the result, compare the summed\nactive_seconds against the max(workflow.submitted - workflow.claimed)\nwall-clock window. Cap at the ceiling.\n\nCaller-tz semantics (added 2026-05-22):\n • `tz` defaults to `UTC` — backwards-compatible no-op for callers\n that don't pass it.\n • When set, `day` rows are calendar days in that tz (computed via\n PG `AT TIME ZONE` over the bucket's stored (day, hour_utc) pair).\n • `from`/`to` are matched against the SAME tz-local day so the\n \"today\" window stays consistent across the filter + group.","operationId":"labeling-time-get_report","security":[{"HTTPBearer":[]}],"parameters":[{"name":"project_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Project Id"}},{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}},{"name":"group_by","in":"query","required":false,"schema":{"type":"string","description":"Comma-separated list of dimensions. Valid: user, class, asset, day. Any non-empty subset, in any order. Each appears as a column on every row; rows are grouped + summed across the rest. Default 'user' (per-labeler totals).","default":"user","title":"Group By"},"description":"Comma-separated list of dimensions. Valid: user, class, asset, day. Any non-empty subset, in any order. Each appears as a column on every row; rows are grouped + summed across the rest. Default 'user' (per-labeler totals)."},{"name":"user_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"description":"Restrict to one user. Managers / admins can pass any user; labelers / reviewers are server-side forced to themselves regardless of what they pass.","title":"User Id"},"description":"Restrict to one user. Managers / admins can pass any user; labelers / reviewers are server-side forced to themselves regardless of what they pass."},{"name":"asset_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"description":"Restrict to one asset. Useful for the asset-detail-view 'you spent X seconds on this asset' card. Pairs with user_id for per-(user, asset) totals.","title":"Asset Id"},"description":"Restrict to one asset. Useful for the asset-detail-view 'you spent X seconds on this asset' card. Pairs with user_id for per-(user, asset) totals."},{"name":"from","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"From"}},{"name":"to","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"To"}},{"name":"tz","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"IANA timezone name (e.g. `Asia/Seoul`, `America/Los_Angeles`). When set, the `day` group dimension and the `from`/`to` range are interpreted in this tz — so a KST user's 'labeled today' card stops resetting at 9am local. Defaults to UTC. Unknown names → 400.","title":"Tz"},"description":"IANA timezone name (e.g. `Asia/Seoul`, `America/Los_Angeles`). When set, the `day` group dimension and the `from`/`to` range are interpreted in this tz — so a KST user's 'labeled today' card stops resetting at 9am local. Defaults to UTC. Unknown names → 400."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LabelingTimeReport"}}}},"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}/projects/{project_id}/versions/{version_id}/exports":{"post":{"tags":["exports"],"summary":"Create Export","description":"Request an export of this frozen version. Async — returns 202 with a\nqueued row; poll `GET /jobs/{job_id}` (or the exports LIST) for status.\n\nTwo idempotency layers (ADR-0048 §Idempotency):\n 1. **Implicit** — `options_hash = sha256(format || canonical_options)`.\n Partial unique index `idx_version_exports_live` enforces \"at most one\n live export per (version, format, options)\". On collision, return the\n existing row + `X-From-Cache: true` header (200 instead of 202).\n 2. **Explicit** — caller-supplied `Idempotency-Key` header (Stripe\n pattern). Unique per tenant via `idx_version_exports_idemp_key`. Lets\n the FE force \"give me THIS specific row again\" or \"force-fresh by\n using a new key.\"","operationId":"exports-create_export","security":[{"HTTPBearer":[]}],"parameters":[{"name":"version_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Version Id"}},{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}},{"name":"project_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Project Id"}},{"name":"Idempotency-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Idempotency-Key"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ExportCreateRequest"}}}},"responses":{"202":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ExportResponse"}}}},"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":["exports"],"summary":"List Exports","description":"Newest first, cursor-paginated by `(created_at, id)`. Optional filters\n(`format`, `status`, `created_at_from/to`) AND together (ADR-0055).","operationId":"exports-list_exports","security":[{"HTTPBearer":[]}],"parameters":[{"name":"version_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Version Id"}},{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}},{"name":"project_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Project Id"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":200,"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":"format","in":"query","required":false,"schema":{"anyOf":[{"$ref":"#/components/schemas/ExportFormat"},{"type":"null"}],"description":"Filter by bundle format (coco|yolo|delta). Invalid values 422.","title":"Format"},"description":"Filter by bundle format (coco|yolo|delta). Invalid values 422."},{"name":"status","in":"query","required":false,"schema":{"anyOf":[{"$ref":"#/components/schemas/ExportStatus"},{"type":"null"}],"description":"Filter by export status (pending|queued|running|ready|failed|expired). Invalid values 422.","title":"Status"},"description":"Filter by export status (pending|queued|running|ready|failed|expired). Invalid values 422."},{"name":"created_at_from","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"description":"Inclusive lower bound (>=) on `created_at` (ISO-8601).","title":"Created At From"},"description":"Inclusive lower bound (>=) on `created_at` (ISO-8601)."},{"name":"created_at_to","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"description":"Exclusive upper bound (<) on `created_at` (ISO-8601).","title":"Created At To"},"description":"Exclusive upper bound (<) on `created_at` (ISO-8601)."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ExportListPage"}}}},"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}/projects/{project_id}/versions/{version_id}/exports/{export_id}/download":{"get":{"tags":["exports"],"summary":"Get Export Download","description":"Returns a fresh 1h-TTL presigned URL on every call.\n\nS3 checks expiry at request start, so 1h covers arbitrary downloads.\nLambda role sessions cap above this regardless of what we pass.\n\nReal presigning lands in Slice 11. Stub raises 425 (pending) since no\nworker has produced a zip yet.","operationId":"exports-get_export_download","security":[{"HTTPBearer":[]}],"parameters":[{"name":"version_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Version Id"}},{"name":"export_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Export Id"}},{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}},{"name":"project_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Project Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ExportDownloadResponse"}}}},"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}/projects/{project_id}/versions/{version_id}/exports/{export_id}":{"delete":{"tags":["exports"],"summary":"Delete Export","description":"Force-delete an export row. Workspace admin only.\n\nDoes NOT delete the S3 object yet (Slice 11 wires the side-effect).\nLifecycle rule will GC it within 7 days regardless.","operationId":"exports-delete_export","security":[{"HTTPBearer":[]}],"parameters":[{"name":"project_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Project Id"}},{"name":"version_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Version Id"}},{"name":"export_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Export 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}/projects/{project_id}/versions/{version_id}/training-runs":{"post":{"tags":["training"],"summary":"Create Training Run","operationId":"training-create_training_run","security":[{"HTTPBearer":[]}],"parameters":[{"name":"project_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Project Id"}},{"name":"version_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Version Id"}},{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TrainingRunCreate"}}}},"responses":{"202":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TrainingRunResponse"}}}},"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":["training"],"summary":"List Training Runs","operationId":"training-list_training_runs","security":[{"HTTPBearer":[]}],"parameters":[{"name":"project_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Project Id"}},{"name":"version_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Version Id"}},{"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"}],"title":"Cursor"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TrainingRunListPage"}}}},"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}/projects/{project_id}/versions/{version_id}/training-runs/{run_id}":{"get":{"tags":["training"],"summary":"Get Training Run","operationId":"training-get_training_run","security":[{"HTTPBearer":[]}],"parameters":[{"name":"project_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Project Id"}},{"name":"version_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Version Id"}},{"name":"run_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Run Id"}},{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TrainingRunResponse"}}}},"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}/projects/{project_id}/versions/{version_id}/training-runs/{run_id}/metrics":{"get":{"tags":["training"],"summary":"Get Training Metrics","description":"The live per-epoch training curve (ADR-0064): train/val loss + val mAP /\nmAP@50 / precision / recall per epoch. Poll alongside the run status while a\nrun is `running` to draw the loss + accuracy charts; rows accrue one per\ncompleted epoch. Cheap: ≤300 rows/run on the `(run, epoch)` index.","operationId":"training-get_training_metrics","security":[{"HTTPBearer":[]}],"parameters":[{"name":"project_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Project Id"}},{"name":"version_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Version Id"}},{"name":"run_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Run Id"}},{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TrainingMetricsResponse"}}}},"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}/projects/{project_id}/versions/{version_id}/training-runs/{run_id}/cancel":{"post":{"tags":["training"],"summary":"Cancel Training Run","operationId":"training-cancel_training_run","security":[{"HTTPBearer":[]}],"parameters":[{"name":"project_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Project Id"}},{"name":"version_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Version Id"}},{"name":"run_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Run Id"}},{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}}],"responses":{"202":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TrainingRunResponse"}}}},"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}/projects/{project_id}/versions/{version_id}/training-stats":{"get":{"tags":["training"],"summary":"Get Training Stats","description":"Pre-train readiness for one task — image/instance counts + per-class\ndistribution, so the FE can show a preview + raise its own warnings before a\nrun starts. O(1) read of the freeze-time snapshot (self-heals legacy\nversions on first call).","operationId":"training-get_training_stats","security":[{"HTTPBearer":[]}],"parameters":[{"name":"project_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Project Id"}},{"name":"version_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Version Id"}},{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}},{"name":"task","in":"query","required":true,"schema":{"$ref":"#/components/schemas/TrainingTask"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TrainingStatsResponse"}}}},"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}/projects/{project_id}/auto-label/config":{"get":{"tags":["auto-label"],"summary":"Get Config","operationId":"auto-label-get_config","security":[{"HTTPBearer":[]}],"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}},{"name":"project_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Project Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AutoLabelConfigResponse"}}}},"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"}}}}}},"put":{"tags":["auto-label"],"summary":"Put Config","description":"Set the project's auto-label policy (manager). Validates the deployment\nexists and resolves the class map; the deployment need not be *ready* at config\ntime (it can be brought up later).","operationId":"auto-label-put_config","security":[{"HTTPBearer":[]}],"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}},{"name":"project_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Project Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AutoLabelConfig"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AutoLabelConfigResponse"}}}},"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}/projects/{project_id}/auto-label/runs":{"post":{"tags":["auto-label"],"summary":"Create Run","description":"Start a batch pre-label run over a scope (manager). Always runs as an ephemeral\nAWS Batch GPU job (ADR-0075): it reads the trained model's weights from S3, so it\nneeds neither a running endpoint nor competes with the interactive deployment's\nserving capacity. Pre-checks config + per-tenant job cap.","operationId":"auto-label-create_run","security":[{"HTTPBearer":[]}],"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}},{"name":"project_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Project Id"}},{"name":"Idempotency-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Idempotency-Key"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AutoLabelRunRequest"}}}},"responses":{"202":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/JobResponse"}}}},"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":["auto-label"],"summary":"List Runs","operationId":"auto-label-list_runs","security":[{"HTTPBearer":[]}],"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}},{"name":"project_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Project Id"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":200,"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."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/JobPage"}}}},"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}/projects/{project_id}/auto-label/runs/{job_id}/cancel":{"post":{"tags":["auto-label"],"summary":"Cancel Run","description":"Cancel an in-flight run (it spends inference $). Sets a terminal `canceled`\nstatus the worker honors at the next chunk boundary.","operationId":"auto-label-cancel_run","security":[{"HTTPBearer":[]}],"parameters":[{"name":"job_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Job Id"}},{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}},{"name":"project_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Project Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/JobResponse"}}}},"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}/projects/{project_id}/assets/{asset_id}/auto-label":{"post":{"tags":["auto-label"],"summary":"Suggest","description":"Run the project's configured deployment on one image and return suggestions\n(not persisted — the labeler accepts/edits, then commits via the normal\nannotation create). Gated on labeler-assist being on and the deployment ready.","operationId":"auto-label-suggest","security":[{"HTTPBearer":[]}],"parameters":[{"name":"asset_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Asset Id"}},{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}},{"name":"project_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Project Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AutoLabelSuggestRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AutoLabelSuggestResponse"}}}},"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}/projects/{project_id}/assets/{asset_id}/segment":{"post":{"tags":["segment"],"summary":"Segment","description":"Promptable segmentation on one image (any project member). Prompts and the\nreturned polygon are in original-image pixels. Returns the single best mask for the\nprompt (or none). `503` when the SAM endpoint isn't running (outside operating hours).","operationId":"segment-segment","security":[{"HTTPBearer":[]}],"parameters":[{"name":"asset_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Asset Id"}},{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}},{"name":"project_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Project Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SegmentRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SegmentResponse"}}}},"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}/models":{"get":{"tags":["models"],"summary":"List Models","description":"The tenant model registry — every trained model across all projects, newest\nfirst. `?project_id=` narrows to one project's lineage.","operationId":"models-list_models","security":[{"HTTPBearer":[]}],"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}},{"name":"project_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"description":"optional: only this project's models","title":"Project Id"},"description":"optional: only this project's models"},{"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."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TrainedModelListPage"}}}},"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}/models/{model_id}":{"get":{"tags":["models"],"summary":"Get Model","operationId":"models-get_model","security":[{"HTTPBearer":[]}],"parameters":[{"name":"model_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Model Id"}},{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TrainedModelResponse"}}}},"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":["models"],"summary":"Update Model","description":"Edit a trained model (ADR-0072) — name and/or description (partial). Name is\nunique per tenant (case-insensitive). Workspace-admin only (ML surface). 409 on\nname collision.","operationId":"models-update_model","security":[{"HTTPBearer":[]}],"parameters":[{"name":"model_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Model Id"}},{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TrainedModelUpdate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TrainedModelResponse"}}}},"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":["models"],"summary":"Delete Model","description":"Delete a trained model (the deletable product) — drops the row and enqueues\nasync S3 cleanup of its weights (ADR-0064; runs are NOT deletable). Hard delete,\nno soft-delete/archive. Cascades to any deployment rows of this model (FK).","operationId":"models-delete_model","security":[{"HTTPBearer":[]}],"parameters":[{"name":"model_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Model 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}/models/{model_id}/deployments":{"post":{"tags":["deployments"],"summary":"Deploy Model","description":"Launch the model on the shared Multi-Model Endpoint (ADR-0081): synchronously upload its\nTriton model tarball to the MME repo → status `active`. Idempotent: an existing live deployment\nof the model is returned instead of a second one. 429 if the tenant is at its concurrent cap.","operationId":"deployments-deploy_model","security":[{"HTTPBearer":[]}],"parameters":[{"name":"model_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Model Id"}},{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeploymentCreate"}}}},"responses":{"202":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeploymentResponse"}}}},"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}/deployments":{"get":{"tags":["deployments"],"summary":"List Deployments","operationId":"deployments-list_deployments","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."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeploymentPage"}}}},"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}/deployments/{deployment_id}":{"get":{"tags":["deployments"],"summary":"Get Deployment","operationId":"deployments-get_deployment","security":[{"HTTPBearer":[]}],"parameters":[{"name":"deployment_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Deployment Id"}},{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeploymentDetail"}}}},"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":["deployments"],"summary":"Stop Deployment","operationId":"deployments-stop_deployment","security":[{"HTTPBearer":[]}],"parameters":[{"name":"deployment_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Deployment 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}/deployments/{deployment_id}/predict":{"post":{"tags":["deployments"],"summary":"Predict","operationId":"deployments-predict","security":[{"HTTPBearer":[]}],"parameters":[{"name":"deployment_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Deployment Id"}},{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PredictRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PredictResponse"}}}},"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}/foundation-models":{"get":{"tags":["foundation-models"],"summary":"List Foundation Models","operationId":"foundation-models-list_foundation_models","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/FoundationModelPage"}}}},"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}/foundation-models/{key}":{"get":{"tags":["foundation-models"],"summary":"Get Foundation Model","operationId":"foundation-models-get_foundation_model","security":[{"HTTPBearer":[]}],"parameters":[{"name":"key","in":"path","required":true,"schema":{"type":"string","title":"Key"}},{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FoundationModelStatus"}}}},"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}/foundation-models/{key}/predict":{"post":{"tags":["foundation-models"],"summary":"Predict","operationId":"foundation-models-predict","security":[{"HTTPBearer":[]}],"parameters":[{"name":"key","in":"path","required":true,"schema":{"type":"string","title":"Key"}},{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ZeroPredictRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ZeroPredictResponse"}}}},"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}/training/catalog":{"get":{"tags":["training"],"summary":"Get Training Catalog","operationId":"training-get_training_catalog","security":[{"HTTPBearer":[]}],"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/CatalogModelResponse"},"title":"Response Training-Get Training Catalog"}}}},"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}/datasets/images/{dataset_id}/assets/{asset_id}/annotations":{"get":{"tags":["projects-visualization"],"summary":"Annotations Multi Source","description":"Return per-source annotations for one asset, for visualization rendering.\n\n`version_id=null` means live current state (queries the annotation table directly).\n`version_id` set → reads from the version's S3 manifest (slice 3b/3c). For 3a we\nonly support live (null); requesting a frozen version returns 501.","operationId":"projects-visualization-annotations_multi_source","security":[{"HTTPBearer":[]}],"parameters":[{"name":"dataset_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Dataset Id"}},{"name":"asset_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Asset Id"}},{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}},{"name":"sources","in":"query","required":true,"schema":{"type":"string","description":"JSON array: [{\"project_id\":\"...\",\"version_id\":null|\"...\"}]","title":"Sources"},"description":"JSON array: [{\"project_id\":\"...\",\"version_id\":null|\"...\"}]"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MultiSourceAnnotations"}}}},"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}/activity":{"get":{"tags":["activity"],"summary":"List Tenant Activity","description":"Tenant-wide activity. Workspace **admin only**.\n\nADR-0035: when no `asset_id` / `subject_id` / `correlation_id` filter is\npassed, reads from the rollup projection — one row per logical action.\nAny of those filters switches to raw `activity_event` for per-event\ndetail. `correlation_id` is the drill-down primitive — pass a rollup\nrow's `id` to expand it.","operationId":"activity-list_tenant_activity","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":"actor","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"description":"Filter to events whose actor is this user (`actor_user_id`). Canonical name (ADR-0055).","title":"Actor"},"description":"Filter to events whose actor is this user (`actor_user_id`). Canonical name (ADR-0055)."},{"name":"action","in":"query","required":false,"schema":{"anyOf":[{"type":"string","maxLength":128},{"type":"null"}],"description":"Prefix match — e.g. `?action=annotation` matches `annotation.submitted`.","title":"Action"},"description":"Prefix match — e.g. `?action=annotation` matches `annotation.submitted`."},{"name":"subject_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"description":"Filter to events about this specific subject (e.g. an annotation UUID).","title":"Subject Id"},"description":"Filter to events about this specific subject (e.g. an annotation UUID)."},{"name":"asset_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"description":"Filter to events tagged with this asset (FE feedback 2026-05-07). Includes annotation/workflow events on this asset, not just events whose subject_id IS the asset. NULL-asset events (project- or tenant-scoped) are excluded.","title":"Asset Id"},"description":"Filter to events tagged with this asset (FE feedback 2026-05-07). Includes annotation/workflow events on this asset, not just events whose subject_id IS the asset. NULL-asset events (project- or tenant-scoped) are excluded."},{"name":"correlation_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"description":"Drill-down filter: every event in the same logical-action group (rolled-up under one row in the default feed). Use the `id` of a rolled-up row (which IS the correlation_id) to expand it into per-event detail. Returns raw events, not the rollup.","title":"Correlation Id"},"description":"Drill-down filter: every event in the same logical-action group (rolled-up under one row in the default feed). Use the `id` of a rolled-up row (which IS the correlation_id) to expand it into per-event detail. Returns raw events, not the rollup."},{"name":"created_at_from","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"description":"Inclusive lower bound (>=) on `created_at` (ISO-8601).","title":"Created At From"},"description":"Inclusive lower bound (>=) on `created_at` (ISO-8601)."},{"name":"created_at_to","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"description":"Exclusive upper bound (<) on `created_at` (ISO-8601).","title":"Created At To"},"description":"Exclusive upper bound (<) on `created_at` (ISO-8601)."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ActivityPage"}}}},"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}/projects/{project_id}/activity":{"get":{"tags":["activity"],"summary":"List Project Activity","description":"Project activity feed. Project member required.\n\n- `manager` / `reviewer` see all events on the project.\n- `labeler` sees only events where they are the actor (own actions only).\n- Workspace `admin` is implicit `manager` (per `get_effective_project_role`).\nNon-members get **403**.","operationId":"activity-list_project_activity","security":[{"HTTPBearer":[]}],"parameters":[{"name":"project_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Project Id"}},{"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":"actor","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"description":"Filter to events whose actor is this user (`actor_user_id`). Canonical name (ADR-0055).","title":"Actor"},"description":"Filter to events whose actor is this user (`actor_user_id`). Canonical name (ADR-0055)."},{"name":"action","in":"query","required":false,"schema":{"anyOf":[{"type":"string","maxLength":128},{"type":"null"}],"title":"Action"}},{"name":"subject_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Subject Id"}},{"name":"asset_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"description":"Filter to events tagged with this asset (FE feedback 2026-05-07).","title":"Asset Id"},"description":"Filter to events tagged with this asset (FE feedback 2026-05-07)."},{"name":"correlation_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"description":"Drill-down — expand one rolled-up row to its per-event detail.","title":"Correlation Id"},"description":"Drill-down — expand one rolled-up row to its per-event detail."},{"name":"created_at_from","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"description":"Inclusive lower bound (>=) on `created_at` (ISO-8601).","title":"Created At From"},"description":"Inclusive lower bound (>=) on `created_at` (ISO-8601)."},{"name":"created_at_to","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"description":"Exclusive upper bound (<) on `created_at` (ISO-8601).","title":"Created At To"},"description":"Exclusive upper bound (<) on `created_at` (ISO-8601)."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ActivityPage"}}}},"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}/datasets/images/{dataset_id}/activity":{"get":{"tags":["activity"],"summary":"List Dataset Activity","description":"Dataset activity feed. **Workspace admin only** — dataset audit is data-ops territory.","operationId":"activity-list_dataset_activity","security":[{"HTTPBearer":[]}],"parameters":[{"name":"dataset_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Dataset Id"}},{"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":"actor","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"description":"Filter to events whose actor is this user (`actor_user_id`). Canonical name (ADR-0055).","title":"Actor"},"description":"Filter to events whose actor is this user (`actor_user_id`). Canonical name (ADR-0055)."},{"name":"action","in":"query","required":false,"schema":{"anyOf":[{"type":"string","maxLength":128},{"type":"null"}],"title":"Action"}},{"name":"subject_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Subject Id"}},{"name":"asset_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"description":"Filter to events tagged with this asset (FE feedback 2026-05-07).","title":"Asset Id"},"description":"Filter to events tagged with this asset (FE feedback 2026-05-07)."},{"name":"correlation_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"description":"Drill-down — expand one rolled-up row to its per-event detail.","title":"Correlation Id"},"description":"Drill-down — expand one rolled-up row to its per-event detail."},{"name":"created_at_from","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"description":"Inclusive lower bound (>=) on `created_at` (ISO-8601).","title":"Created At From"},"description":"Inclusive lower bound (>=) on `created_at` (ISO-8601)."},{"name":"created_at_to","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"description":"Exclusive upper bound (<) on `created_at` (ISO-8601).","title":"Created At To"},"description":"Exclusive upper bound (<) on `created_at` (ISO-8601)."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ActivityPage"}}}},"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"}}}}}}},"/health":{"get":{"summary":"Health","operationId":"default-health","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":{"type":"string"},"type":"object","title":"Response Default-Health"}}}},"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"}}}}}}}},"components":{"schemas":{"ActivityEventResponse":{"properties":{"id":{"type":"string","format":"uuid","title":"Id"},"actor_user_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Actor User Id"},"actor_email":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Actor Email"},"actor_key_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Actor Key Id"},"actor_system":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Actor System"},"action":{"type":"string","title":"Action"},"subject_type":{"type":"string","title":"Subject Type"},"subject_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Subject Id"},"project_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Project Id"},"dataset_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Dataset Id"},"asset_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Asset Id"},"metadata":{"additionalProperties":true,"type":"object","title":"Metadata"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"count":{"type":"integer","title":"Count","default":1},"event_count":{"type":"integer","title":"Event Count","default":1},"sample_subject_ids":{"items":{"type":"string","format":"uuid"},"type":"array","title":"Sample Subject Ids"},"first_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"First At"},"last_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Last At"}},"type":"object","required":["id","actor_user_id","actor_system","action","subject_type","subject_id","project_id","dataset_id","asset_id","metadata","created_at"],"title":"ActivityEventResponse"},"ActivityPage":{"properties":{"items":{"items":{"$ref":"#/components/schemas/ActivityEventResponse"},"type":"array","title":"Items"},"next_cursor":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Next Cursor"}},"type":"object","required":["items"],"title":"ActivityPage"},"AnchorKind":{"type":"string","enum":["asset","point","box","annotation"],"title":"AnchorKind"},"AnnotationBatchCreate":{"properties":{"annotations":{"items":{"$ref":"#/components/schemas/AnnotationCreate"},"type":"array","maxItems":1000,"minItems":1,"title":"Annotations"},"replace":{"type":"boolean","title":"Replace","default":false}},"type":"object","required":["annotations"],"title":"AnnotationBatchCreate","description":"Body for `POST /{pid}/assets/{aid}/annotations/batch-create` — sync, one\nasset (asset in the path). `source` is always `manual` (the labeling path).\n`replace=true` deletes the asset's existing annotations before inserting."},"AnnotationBatchDeleteRequest":{"properties":{"ids":{"items":{"type":"string","format":"uuid"},"type":"array","maxItems":500,"minItems":1,"title":"Ids"}},"type":"object","required":["ids"],"title":"AnnotationBatchDeleteRequest","description":"POST `batch-delete` body. Hard-cap 500 ids per call (ADR-0029 perf budget)."},"AnnotationBatchDeleteResponse":{"properties":{"total":{"type":"integer","title":"Total","description":"Number of ids requested."},"deleted":{"type":"integer","title":"Deleted","description":"Number of ids that matched a row in this project and were deleted."},"skipped":{"type":"integer","title":"Skipped","description":"Number of requested ids that matched no row (already deleted, wrong project, or never existed).","default":0},"results":{"items":{"$ref":"#/components/schemas/AnnotationBatchDeleteResultItem"},"type":"array","title":"Results","description":"Per-id outcome, one entry per requested id."}},"type":"object","required":["total","deleted","results"],"title":"AnnotationBatchDeleteResponse","description":"Canonical sync batch-op envelope (ADR-0041): buckets sum to total\n(`total == deleted + skipped`) and `len(results) == total`. Shape mirrors\nthe asset `batch-delete` response so the FE has one bucket model."},"AnnotationBatchDeleteResultItem":{"properties":{"id":{"type":"string","format":"uuid","title":"Id"},"status":{"type":"string","enum":["deleted","skipped"],"title":"Status"}},"type":"object","required":["id","status"],"title":"AnnotationBatchDeleteResultItem"},"AnnotationBatchEditCreate":{"properties":{"id":{"type":"string","format":"uuid","title":"Id"},"asset_id":{"type":"string","format":"uuid","title":"Asset Id"},"class_id":{"type":"string","format":"uuid","title":"Class Id"},"type":{"$ref":"#/components/schemas/AnnotationType"},"geometry":{"anyOf":[{"oneOf":[{"$ref":"#/components/schemas/BboxGeometry"},{"$ref":"#/components/schemas/PolygonGeometry"},{"$ref":"#/components/schemas/KeypointGeometry"}],"discriminator":{"propertyName":"type","mapping":{"bbox":"#/components/schemas/BboxGeometry","keypoint":"#/components/schemas/KeypointGeometry","polygon":"#/components/schemas/PolygonGeometry"}}},{"type":"null"}],"title":"Geometry"},"data":{"additionalProperties":true,"type":"object","title":"Data"},"instance_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Instance Id"},"parent_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Parent Id"},"locked":{"type":"boolean","title":"Locked","default":false}},"type":"object","required":["id","asset_id","class_id","type"],"title":"AnnotationBatchEditCreate","description":"A new annotation in a batch edit (ADR-0092). `id` is the caller's\nclient-minted UUID — it IS the shape's primary key and must be unique within\nthis request. The create upserts on the PK (`id`), so a retry is idempotent.\n`parent_id` names this shape's parent by the parent's `id` — either another\ncreate in the same batch or an existing shape (the self-FK is satisfied at\nstatement end). `source` is always `manual` (the editor path)."},"AnnotationBatchEditRequest":{"properties":{"creates":{"items":{"$ref":"#/components/schemas/AnnotationBatchEditCreate"},"type":"array","title":"Creates"},"updates":{"items":{"$ref":"#/components/schemas/AnnotationBatchEditUpdate"},"type":"array","title":"Updates"},"deletes":{"items":{"type":"string","format":"uuid"},"type":"array","title":"Deletes"}},"type":"object","title":"AnnotationBatchEditRequest","description":"Body for `POST /{pid}/annotations/batch`. At least one bucket non-empty;\nthe combined op count is capped at `MAX_SYNC_BATCH_SIZE` and the raw body at\n`MAX_SYNC_BATCH_BYTES` (enforced on the route before parse). `deletes` is a\nlist of annotation ids and is idempotent — ids already gone are no-ops, not\nerrors."},"AnnotationBatchEditResponse":{"properties":{"created":{"items":{"type":"string","format":"uuid"},"type":"array","title":"Created"},"updated":{"items":{"type":"string","format":"uuid"},"type":"array","title":"Updated"},"deleted":{"items":{"type":"string","format":"uuid"},"type":"array","title":"Deleted"}},"type":"object","required":["created","updated","deleted"],"title":"AnnotationBatchEditResponse"},"AnnotationBatchEditUpdate":{"properties":{"id":{"type":"string","format":"uuid","title":"Id"},"class_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Class Id"},"geometry":{"anyOf":[{"oneOf":[{"$ref":"#/components/schemas/BboxGeometry"},{"$ref":"#/components/schemas/PolygonGeometry"},{"$ref":"#/components/schemas/KeypointGeometry"}],"discriminator":{"propertyName":"type","mapping":{"bbox":"#/components/schemas/BboxGeometry","keypoint":"#/components/schemas/KeypointGeometry","polygon":"#/components/schemas/PolygonGeometry"}}},{"type":"null"}],"title":"Geometry"},"data":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Data"},"instance_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Instance Id"},"locked":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Locked"}},"type":"object","required":["id"],"title":"AnnotationBatchEditUpdate","description":"A partial update of an existing annotation by `id`. Only the supplied\nfields change; omitted fields are left as-is. `type` is immutable (changing\ngeometry kind = delete + create), so geometry is validated against the\nannotation's stored type server-side."},"AnnotationBlameEntry":{"properties":{"submit_seq":{"type":"integer","title":"Submit Seq","description":"Monotonic sequence number of this commit within the asset's history."},"reason":{"$ref":"#/components/schemas/RevisionReason","description":"Why this revision was created (e.g. submitted, approved, rejected)."},"actor_user_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Actor User Id","description":"ID of the user who made this commit, or null if not attributable to a user."},"actor_role":{"anyOf":[{"$ref":"#/components/schemas/ProjectRole"},{"type":"null"}],"description":"The committing user's effective project role at commit time, or null."},"committed_at":{"type":"string","format":"date-time","title":"Committed At","description":"Timestamp when this revision was committed."},"class_id":{"type":"string","format":"uuid","title":"Class Id","description":"ID of the annotation class this shape belongs to."},"type":{"$ref":"#/components/schemas/AnnotationType","description":"Kind of annotation (bbox, polygon, keypoint, classification)."},"geometry":{"anyOf":[{"oneOf":[{"$ref":"#/components/schemas/BboxGeometryRead"},{"$ref":"#/components/schemas/PolygonGeometryRead"},{"$ref":"#/components/schemas/KeypointGeometryRead"}],"discriminator":{"propertyName":"type","mapping":{"bbox":"#/components/schemas/BboxGeometryRead","keypoint":"#/components/schemas/KeypointGeometryRead","polygon":"#/components/schemas/PolygonGeometryRead"}}},{"type":"null"}],"title":"Geometry","description":"Typed geometry snapshot (read union, ADR-0104); null for classification annotations."},"data":{"additionalProperties":true,"type":"object","title":"Data","description":"Class-attribute values and other annotation metadata."}},"type":"object","required":["submit_seq","reason","actor_user_id","actor_role","committed_at","class_id","type","geometry","data"],"title":"AnnotationBlameEntry","description":"One shape's state at one commit — the per-annotation blame timeline."},"AnnotationBlamePage":{"properties":{"items":{"items":{"$ref":"#/components/schemas/AnnotationBlameEntry"},"type":"array","title":"Items","description":"This page of blame entries."},"next_cursor":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Next Cursor","description":"Opaque cursor for the next page, or null when this is the last page."}},"type":"object","required":["items"],"title":"AnnotationBlamePage","description":"A page of one annotation's blame timeline (ADR-0012 cursor pagination)."},"AnnotationCreate":{"properties":{"id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Id"},"class_id":{"type":"string","format":"uuid","title":"Class Id"},"type":{"$ref":"#/components/schemas/AnnotationType"},"geometry":{"anyOf":[{"oneOf":[{"$ref":"#/components/schemas/BboxGeometry"},{"$ref":"#/components/schemas/PolygonGeometry"},{"$ref":"#/components/schemas/KeypointGeometry"}],"discriminator":{"propertyName":"type","mapping":{"bbox":"#/components/schemas/BboxGeometry","keypoint":"#/components/schemas/KeypointGeometry","polygon":"#/components/schemas/PolygonGeometry"}}},{"type":"null"}],"title":"Geometry"},"data":{"additionalProperties":true,"type":"object","title":"Data"},"instance_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Instance Id"},"parent_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Parent Id"},"locked":{"type":"boolean","title":"Locked","default":false}},"type":"object","required":["class_id","type"],"title":"AnnotationCreate"},"AnnotationImportInitRequest":{"properties":{"size_bytes":{"type":"integer","maximum":268435456.0,"exclusiveMinimum":0.0,"title":"Size Bytes","description":"NDJSON file size in bytes. Hard cap 256 MB; over → 422 here, 413 on the route. The presign pins this exact size at S3."}},"type":"object","required":["size_bytes"],"title":"AnnotationImportInitRequest"},"AnnotationImportInitResponse":{"properties":{"import_id":{"type":"string","format":"uuid","title":"Import Id"},"upload_url":{"type":"string","title":"Upload Url"},"expires_at":{"type":"string","format":"date-time","title":"Expires At"}},"type":"object","required":["import_id","upload_url","expires_at"],"title":"AnnotationImportInitResponse","description":"`import_id` is what you pass to `bulk-create`. PUT the raw NDJSON bytes to\n`upload_url` (exactly `size_bytes` long) before kicking the job."},"AnnotationListItem":{"properties":{"id":{"type":"string","format":"uuid","title":"Id","description":"Annotation id (client-minted UUID, ADR-0092)."},"asset_id":{"type":"string","format":"uuid","title":"Asset Id","description":"Id of the asset this annotation is on."},"class_id":{"type":"string","format":"uuid","title":"Class Id","description":"Id of the project class this annotation belongs to."},"type":{"$ref":"#/components/schemas/AnnotationType","description":"Annotation type (bbox, polygon, keypoint, or classification)."},"parent_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Parent Id","description":"Parent annotation id, if this is nested."},"instance_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Instance Id","description":"Instance grouping id linking annotations of the same object."},"geometry":{"anyOf":[{"oneOf":[{"$ref":"#/components/schemas/BboxGeometryRead"},{"$ref":"#/components/schemas/PolygonGeometryRead"},{"$ref":"#/components/schemas/KeypointGeometryRead"}],"discriminator":{"propertyName":"type","mapping":{"bbox":"#/components/schemas/BboxGeometryRead","keypoint":"#/components/schemas/KeypointGeometryRead","polygon":"#/components/schemas/PolygonGeometryRead"}}},{"type":"null"}],"title":"Geometry","description":"Pixel-coordinate typed geometry (bbox/polygon/keypoint, ADR-0091); null for classification."},"data":{"additionalProperties":true,"type":"object","title":"Data","description":"Free-form attribute/classification values for this annotation."},"source":{"$ref":"#/components/schemas/AnnotationSource","description":"How the annotation was produced (manual, imported, or model)."},"locked":{"type":"boolean","title":"Locked","description":"Per-shape protection lock (ADR-0084); absent in legacy snapshots → false.","default":false},"created_by":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Created By","description":"User who created the annotation, if known."},"created_at":{"type":"string","format":"date-time","title":"Created At","description":"Creation timestamp."},"updated_at":{"type":"string","format":"date-time","title":"Updated At","description":"Last-update timestamp."},"asset":{"anyOf":[{"$ref":"#/components/schemas/AssetSummary"},{"type":"null"}]},"class":{"anyOf":[{"$ref":"#/components/schemas/ClassRef"},{"type":"null"}]}},"type":"object","required":["id","asset_id","class_id","type","parent_id","instance_id","geometry","data","source","created_by","created_at","updated_at"],"title":"AnnotationListItem","description":"List-endpoint item: AnnotationResponse plus optional embedded asset + the class\nthe annotation is an instance of (ADR-0090; wire key `class`)."},"AnnotationListPage":{"properties":{"items":{"items":{"$ref":"#/components/schemas/AnnotationListItem"},"type":"array","title":"Items"},"next_cursor":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Next Cursor"},"total":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Total"}},"type":"object","required":["items"],"title":"AnnotationListPage"},"AnnotationResponse":{"properties":{"id":{"type":"string","format":"uuid","title":"Id","description":"Annotation id (client-minted UUID, ADR-0092)."},"asset_id":{"type":"string","format":"uuid","title":"Asset Id","description":"Id of the asset this annotation is on."},"class_id":{"type":"string","format":"uuid","title":"Class Id","description":"Id of the project class this annotation belongs to."},"type":{"$ref":"#/components/schemas/AnnotationType","description":"Annotation type (bbox, polygon, keypoint, or classification)."},"parent_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Parent Id","description":"Parent annotation id, if this is nested."},"instance_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Instance Id","description":"Instance grouping id linking annotations of the same object."},"geometry":{"anyOf":[{"oneOf":[{"$ref":"#/components/schemas/BboxGeometryRead"},{"$ref":"#/components/schemas/PolygonGeometryRead"},{"$ref":"#/components/schemas/KeypointGeometryRead"}],"discriminator":{"propertyName":"type","mapping":{"bbox":"#/components/schemas/BboxGeometryRead","keypoint":"#/components/schemas/KeypointGeometryRead","polygon":"#/components/schemas/PolygonGeometryRead"}}},{"type":"null"}],"title":"Geometry","description":"Pixel-coordinate typed geometry (bbox/polygon/keypoint, ADR-0091); null for classification."},"data":{"additionalProperties":true,"type":"object","title":"Data","description":"Free-form attribute/classification values for this annotation."},"source":{"$ref":"#/components/schemas/AnnotationSource","description":"How the annotation was produced (manual, imported, or model)."},"locked":{"type":"boolean","title":"Locked","description":"Per-shape protection lock (ADR-0084); absent in legacy snapshots → false.","default":false},"created_by":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Created By","description":"User who created the annotation, if known."},"created_at":{"type":"string","format":"date-time","title":"Created At","description":"Creation timestamp."},"updated_at":{"type":"string","format":"date-time","title":"Updated At","description":"Last-update timestamp."}},"type":"object","required":["id","asset_id","class_id","type","parent_id","instance_id","geometry","data","source","created_by","created_at","updated_at"],"title":"AnnotationResponse"},"AnnotationSource":{"type":"string","enum":["manual","imported","model"],"title":"AnnotationSource"},"AnnotationType":{"type":"string","enum":["bbox","polygon","keypoint","classification"],"title":"AnnotationType"},"AnnotationUpdate":{"properties":{"class_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Class Id"},"geometry":{"anyOf":[{"oneOf":[{"$ref":"#/components/schemas/BboxGeometry"},{"$ref":"#/components/schemas/PolygonGeometry"},{"$ref":"#/components/schemas/KeypointGeometry"}],"discriminator":{"propertyName":"type","mapping":{"bbox":"#/components/schemas/BboxGeometry","keypoint":"#/components/schemas/KeypointGeometry","polygon":"#/components/schemas/PolygonGeometry"}}},{"type":"null"}],"title":"Geometry"},"data":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Data"},"instance_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Instance Id"},"locked":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Locked"}},"type":"object","title":"AnnotationUpdate"},"AnnotationsBySource":{"properties":{"project_id":{"type":"string","format":"uuid","title":"Project Id"},"version_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Version Id"},"shapes":{"items":{"$ref":"#/components/schemas/AnnotationResponse"},"type":"array","title":"Shapes"},"annotations":{"items":{"$ref":"#/components/schemas/AnnotationResponse"},"type":"array","title":"Annotations"}},"type":"object","required":["project_id","version_id"],"title":"AnnotationsBySource","description":"Per-source bucket for the multi-source visualization endpoint.\n\n`annotations` is a legacy alias of `shapes`; deprecated."},"ApiKeyCreate":{"properties":{"name":{"type":"string","maxLength":64,"minLength":1,"title":"Name","description":"User-supplied label so you can recognize the key later (e.g. \"ci-cd\", \"my-laptop\")."},"expires_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Expires At","description":"ISO 8601 timestamp after which the key stops authenticating; null or omitted means the key never expires."}},"type":"object","required":["name"],"title":"ApiKeyCreate","description":"Body for `POST /api-keys`."},"ApiKeyCreated":{"properties":{"id":{"type":"string","format":"uuid","title":"Id","description":"Server-assigned unique id of the API key."},"name":{"type":"string","title":"Name","description":"The user-supplied label for the key."},"prefix":{"type":"string","title":"Prefix","description":"Short non-secret prefix of the key (e.g. `sbd_pk_a3f4…`), used to identify which key is which."},"plaintext":{"type":"string","title":"Plaintext","description":"The full secret key value. **Returned exactly once, here** — the server only retains a hash. Store it securely; it cannot be recovered."},"created_at":{"type":"string","format":"date-time","title":"Created At","description":"When the key was created."},"expires_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Expires At","description":"When the key expires; null means it never expires."}},"type":"object","required":["id","name","prefix","plaintext","created_at","expires_at"],"title":"ApiKeyCreated","description":"Response for `POST /api-keys`. **The `plaintext` field is returned\nexactly once and never again** — store it securely (env var, secret manager).\nSubsequent calls to GET return everything except the plaintext."},"ApiKeyList":{"properties":{"items":{"items":{"$ref":"#/components/schemas/ApiKeySummary"},"type":"array","title":"Items"},"next_cursor":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Next Cursor"}},"type":"object","required":["items"],"title":"ApiKeyList"},"ApiKeySummary":{"properties":{"id":{"type":"string","format":"uuid","title":"Id","description":"Server-assigned unique id of the API key."},"name":{"type":"string","title":"Name","description":"The user-supplied label for the key."},"prefix":{"type":"string","title":"Prefix","description":"Short non-secret prefix of the key (e.g. `sbd_pk_a3f4…`), used to identify which key is which."},"created_at":{"type":"string","format":"date-time","title":"Created At","description":"When the key was created."},"last_used_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Last Used At","description":"When the key was last used to authenticate; null if never used."},"expires_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Expires At","description":"When the key expires; null means it never expires."}},"type":"object","required":["id","name","prefix","created_at","last_used_at","expires_at"],"title":"ApiKeySummary","description":"List response item. Never carries the plaintext."},"AssetDownloadUrl":{"properties":{"url":{"type":"string","title":"Url"},"expires_at":{"type":"string","format":"date-time","title":"Expires At"}},"type":"object","required":["url","expires_at"],"title":"AssetDownloadUrl","description":"Presigned GET URL for asset content (canvas rendering)."},"AssetFilter":{"properties":{"created_at_from":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Created At From"},"created_at_to":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Created At To"},"uploaded_by":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Uploaded By"},"q":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Q"},"status":{"anyOf":[{"$ref":"#/components/schemas/AssetStatus"},{"type":"null"}]},"content_type":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Content Type"}},"additionalProperties":false,"type":"object","title":"AssetFilter"},"AssetPage":{"properties":{"items":{"items":{"$ref":"#/components/schemas/AssetResponse"},"type":"array","title":"Items"},"next_cursor":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Next Cursor"},"total":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Total"}},"type":"object","required":["items"],"title":"AssetPage"},"AssetProjectMembership":{"properties":{"id":{"type":"string","format":"uuid","title":"Id"},"name":{"type":"string","title":"Name"}},"type":"object","required":["id","name"],"title":"AssetProjectMembership","description":"Compact project ref for the asset-list `?include=projects` embed (FE feedback 2026-05-07)."},"AssetProperties":{"properties":{"width":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Width"},"height":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Height"},"format":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Format"}},"additionalProperties":true,"type":"object","title":"AssetProperties","description":"Extracted image facts persisted on `ImageAsset.properties` (JSONB) and\nechoed on the wire. Written once by `extract.extract_image` (typed →\n`model_dump`); read leniently everywhere else (job_worker freeze, exports,\nasset responses). `{}` when extraction soft-failed, so every field is\nOptional-with-default — a legacy row that predates a later-added key, or a\nfailed extraction, must still parse. `extra=\"allow\"` keeps unknown future\nkeys through a round-trip rather than dropping them."},"AssetResponse":{"properties":{"id":{"type":"string","format":"uuid","title":"Id","description":"Asset identifier"},"filename":{"type":"string","title":"Filename","description":"Original file name"},"size_bytes":{"type":"integer","title":"Size Bytes","description":"Stored object size in bytes"},"content_type":{"type":"string","title":"Content Type","description":"MIME type of the stored object"},"status":{"$ref":"#/components/schemas/AssetStatus","description":"Processing lifecycle state of the asset"},"properties":{"$ref":"#/components/schemas/AssetProperties","description":"Extracted metadata (dimensions, etc.)"},"uploaded_by":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Uploaded By","description":"User who uploaded the asset; null if the user was since removed"},"embedding_status":{"$ref":"#/components/schemas/EmbeddingStatus","description":"Semantic-search coverage state (ADR-0033/0042)"},"created_at":{"type":"string","format":"date-time","title":"Created At","description":"When the asset row was created"},"projects":{"anyOf":[{"items":{"$ref":"#/components/schemas/AssetProjectMembership"},"type":"array"},{"type":"null"}],"title":"Projects"},"thumbnail_url":{"anyOf":[{"$ref":"#/components/schemas/AssetDownloadUrl"},{"type":"null"}]}},"type":"object","required":["id","filename","size_bytes","content_type","status","properties","uploaded_by","embedding_status","created_at"],"title":"AssetResponse"},"AssetStatePage":{"properties":{"items":{"items":{"$ref":"#/components/schemas/AssetStateResponse"},"type":"array","title":"Items"},"next_cursor":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Next Cursor"},"total":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Total"}},"type":"object","required":["items"],"title":"AssetStatePage"},"AssetStateResponse":{"properties":{"project_id":{"type":"string","format":"uuid","title":"Project Id","description":"Project id."},"asset_id":{"type":"string","format":"uuid","title":"Asset Id","description":"Asset id."},"asset":{"anyOf":[{"$ref":"#/components/schemas/AssetSummary"},{"type":"null"}],"description":"Asset summary; populated when the caller JOINed. FE uses this for cards."},"status":{"$ref":"#/components/schemas/WorkflowStatus","description":"Workflow status (to_label, in_progress, in_review, or done)."},"assigned_to":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Assigned To","description":"User the asset is assigned to, if any."},"assigned_to_user":{"anyOf":[{"$ref":"#/components/schemas/UserSummary"},{"type":"null"}],"description":"Assignee profile; populated when the caller JOINed."},"reviewer_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Reviewer Id","description":"Reviewer user id, if any."},"reviewer_user":{"anyOf":[{"$ref":"#/components/schemas/UserSummary"},{"type":"null"}],"description":"Reviewer profile; populated when the caller JOINed."},"rework_count":{"type":"integer","title":"Rework Count","description":"How many times the asset was sent back for rework."},"last_reject_reason":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Last Reject Reason","description":"Reason from the most recent rejection."},"skipped_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Skipped At","description":"When the asset was skipped (ADR-0110); null unless skipped."},"last_submitted_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Last Submitted At","description":"When the asset was last submitted."},"last_reviewed_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Last Reviewed At","description":"When the asset was last reviewed."},"last_action_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Last Action At","description":"Timestamp of the last workflow action."},"updated_at":{"type":"string","format":"date-time","title":"Updated At","description":"Last-update timestamp."},"tags":{"items":{"type":"string"},"type":"array","title":"Tags","description":"Per-project tags on this (project, asset) pair (ADR-0050)."},"annotation_source_counts":{"anyOf":[{"additionalProperties":{"type":"integer"},"type":"object"},{"type":"null"}],"title":"Annotation Source Counts","description":"Per-provenance live annotation counts `{manual, imported, model}` (all keys always present, zeros included). Populated only with `?include=annotation_source_counts`."},"classifications":{"anyOf":[{"items":{"$ref":"#/components/schemas/ClassificationSummary"},"type":"array"},{"type":"null"}],"title":"Classifications","description":"Answered image-level classifications on the asset. Populated only with `?include=classifications`."}},"type":"object","required":["project_id","asset_id","status","assigned_to","reviewer_id","rework_count","last_reject_reason","last_submitted_at","last_reviewed_at","last_action_at","updated_at"],"title":"AssetStateResponse"},"AssetStatus":{"type":"string","enum":["pending","processing","ready","failed","deleted"],"title":"AssetStatus"},"AssetSummary":{"properties":{"id":{"type":"string","format":"uuid","title":"Id"},"filename":{"type":"string","title":"Filename"},"content_type":{"type":"string","title":"Content Type"},"status":{"$ref":"#/components/schemas/AssetStatus"},"properties":{"$ref":"#/components/schemas/AssetProperties"},"thumbnail_url":{"anyOf":[{"$ref":"#/components/schemas/AssetDownloadUrl"},{"type":"null"}]}},"type":"object","required":["id","filename","content_type","status","properties"],"title":"AssetSummary","description":"Compact asset reference for embedding in project asset-state rows so the\nFE can render filename + dimensions without N+1 lookups. Does NOT include a\ndownload URL — generating presigned URLs per row would be expensive on lists;\nthe FE calls /download-url per asset when actually rendering the canvas."},"AssetUploadCompleteRequest":{"properties":{"s3_key":{"type":"string","title":"S3 Key","description":"Echoed from upload-init response"},"parts":{"items":{"$ref":"#/components/schemas/MultipartCompletePart"},"type":"array","minItems":1,"title":"Parts"}},"type":"object","required":["s3_key","parts"],"title":"AssetUploadCompleteRequest"},"AssetUploadInitBatchItem":{"properties":{"client_ref":{"type":"string","maxLength":512,"minLength":1,"title":"Client Ref","description":"Opaque caller-supplied token echoed back on the matching response item"},"filename":{"type":"string","maxLength":512,"minLength":1,"title":"Filename","description":"Original file name"},"size_bytes":{"type":"integer","exclusiveMinimum":0.0,"title":"Size Bytes","description":"Declared size; validated against per-format max"},"content_type":{"type":"string","maxLength":128,"minLength":1,"title":"Content Type","description":"MIME; validated against the kind's accepted list (ADR-0017)"}},"type":"object","required":["client_ref","filename","size_bytes","content_type"],"title":"AssetUploadInitBatchItem","description":"One file in a batch upload-init request. `client_ref` is an opaque\nstring the caller can use to map the response back to its source — the\nSDK uses the local file path, but anything goes."},"AssetUploadInitBatchItemResponse":{"properties":{"client_ref":{"type":"string","title":"Client Ref"},"upload_type":{"type":"string","enum":["single","error"],"title":"Upload Type"},"s3_key":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"S3 Key"},"upload_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Upload Url"},"expires_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Expires At"},"error_code":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error Code"},"error_message":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error Message"}},"type":"object","required":["client_ref","upload_type"],"title":"AssetUploadInitBatchItemResponse","description":"Per-item outcome. Either a presigned URL (`upload_type=\"single\"`) or\nan error (`upload_type=\"error\"`) — files in the batch can fail\nindependently (e.g., wrong MIME on one file) without poisoning the rest."},"AssetUploadInitBatchRequest":{"properties":{"items":{"items":{"$ref":"#/components/schemas/AssetUploadInitBatchItem"},"type":"array","maxItems":100,"minItems":1,"title":"Items"}},"type":"object","required":["items"],"title":"AssetUploadInitBatchRequest"},"AssetUploadInitBatchResponse":{"properties":{"items":{"items":{"$ref":"#/components/schemas/AssetUploadInitBatchItemResponse"},"type":"array","title":"Items"}},"type":"object","required":["items"],"title":"AssetUploadInitBatchResponse","description":"All items returned, in input order. Caller cross-references via\n`client_ref`. Files >MULTIPART_THRESHOLD_BYTES are NOT served by the\nbatch endpoint (the multipart path needs per-part presigning + a\ncomplete RPC); those return error_code='USE_PER_FILE_INIT' so the\ncaller knows to fall back to /upload-init for that one."},"AssetUploadInitMultipartResponse":{"properties":{"upload_type":{"type":"string","const":"multipart","title":"Upload Type","default":"multipart"},"s3_key":{"type":"string","title":"S3 Key"},"upload_id":{"type":"string","title":"Upload Id"},"parts":{"items":{"$ref":"#/components/schemas/MultipartPartUrl"},"type":"array","title":"Parts"},"expires_at":{"type":"string","format":"date-time","title":"Expires At"}},"type":"object","required":["s3_key","upload_id","parts","expires_at"],"title":"AssetUploadInitMultipartResponse"},"AssetUploadInitRequest":{"properties":{"filename":{"type":"string","maxLength":512,"minLength":1,"title":"Filename"},"size_bytes":{"type":"integer","exclusiveMinimum":0.0,"title":"Size Bytes","description":"Declared size; validated against per-format max"},"content_type":{"type":"string","maxLength":128,"minLength":1,"title":"Content Type","description":"MIME; validated against the kind's accepted list (ADR-0017)"}},"type":"object","required":["filename","size_bytes","content_type"],"title":"AssetUploadInitRequest"},"AssetUploadInitSinglePartResponse":{"properties":{"upload_type":{"type":"string","const":"single","title":"Upload Type","default":"single"},"s3_key":{"type":"string","title":"S3 Key"},"upload_url":{"type":"string","title":"Upload Url"},"expires_at":{"type":"string","format":"date-time","title":"Expires At"}},"type":"object","required":["s3_key","upload_url","expires_at"],"title":"AssetUploadInitSinglePartResponse"},"AssetsBatchDeleteRequest":{"properties":{"asset_ids":{"items":{"type":"string","format":"uuid"},"type":"array","maxItems":1000,"minItems":1,"title":"Asset Ids"}},"type":"object","required":["asset_ids"],"title":"AssetsBatchDeleteRequest","description":"Body for `POST /assets/batch-delete` — sync soft-delete on an explicit\nselection of assets (1 ≤ N ≤ MAX_SYNC_BATCH_SIZE)."},"AssetsBatchDeleteResponse":{"properties":{"total":{"type":"integer","title":"Total"},"deleted":{"type":"integer","title":"Deleted"},"skipped":{"type":"integer","title":"Skipped"},"results":{"items":{"$ref":"#/components/schemas/AssetsBatchDeleteResultItem"},"type":"array","title":"Results"}},"type":"object","required":["total","deleted","skipped","results"],"title":"AssetsBatchDeleteResponse"},"AssetsBatchDeleteResultItem":{"properties":{"id":{"type":"string","format":"uuid","title":"Id"},"status":{"type":"string","enum":["deleted","skipped"],"title":"Status"}},"type":"object","required":["id","status"],"title":"AssetsBatchDeleteResultItem"},"AssignRequest":{"properties":{"user_id":{"type":"string","format":"uuid","title":"User Id"}},"type":"object","required":["user_id"],"title":"AssignRequest"},"AssignmentMode":{"type":"string","enum":["self_claim","manual"],"title":"AssignmentMode"},"AttributeCreate":{"properties":{"name":{"type":"string","maxLength":255,"minLength":1,"title":"Name"},"color":{"type":"string","pattern":"^#[0-9A-Fa-f]{6}$","title":"Color","default":"#888888"},"classification_config":{"$ref":"#/components/schemas/ClassificationConfig"}},"type":"object","required":["name","classification_config"],"title":"AttributeCreate","description":"An attribute = a classification owned by an object class. Always a question, so\n`classification_config` is required."},"AttributeResponse":{"properties":{"id":{"type":"string","format":"uuid","title":"Id"},"name":{"type":"string","title":"Name"},"owner_class_id":{"type":"string","format":"uuid","title":"Owner Class Id"},"color":{"type":"string","title":"Color"},"archived":{"type":"boolean","title":"Archived"},"classification_config":{"anyOf":[{"$ref":"#/components/schemas/ClassificationConfig"},{"type":"null"}]},"created_at":{"type":"string","format":"date-time","title":"Created At"},"updated_at":{"type":"string","format":"date-time","title":"Updated At"}},"type":"object","required":["id","name","owner_class_id","color","archived","created_at","updated_at"],"title":"AttributeResponse"},"AttributeUpdate":{"properties":{"name":{"anyOf":[{"type":"string","maxLength":255,"minLength":1},{"type":"null"}],"title":"Name"},"color":{"anyOf":[{"type":"string","pattern":"^#[0-9A-Fa-f]{6}$"},{"type":"null"}],"title":"Color"},"archived":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Archived"},"classification_config":{"anyOf":[{"$ref":"#/components/schemas/ClassificationConfig"},{"type":"null"}]}},"type":"object","title":"AttributeUpdate"},"AuthTokens":{"properties":{"access_token":{"type":"string","title":"Access Token","description":"JWT access token; send as `Authorization: Bearer <token>`."},"refresh_token":{"type":"string","title":"Refresh Token","description":"Long-lived token; exchange via /auth/refresh."}},"type":"object","required":["access_token","refresh_token"],"title":"AuthTokens","description":"Tokens issued by WorkOS. The access token is a short-lived (~5 min) JWT sent\nas `Authorization: Bearer <token>`; the refresh token trades for a new pair via\n/auth/refresh. (WorkOS issues no separate id_token — identity claims live in the\naccess token.)"},"AuthorizeResponse":{"properties":{"authorization_url":{"type":"string","title":"Authorization Url","description":"Hosted AuthKit URL to redirect the user to."}},"type":"object","required":["authorization_url"],"title":"AuthorizeResponse","description":"The hosted-AuthKit URL the FE redirects the browser to for sign-in/sign-up\n(ADR-0053). After login, WorkOS redirects back to the configured callback with\na one-time `code`."},"AutoLabelConfig":{"properties":{"model_ref":{"$ref":"#/components/schemas/ModelRef"},"class_map":{"anyOf":[{"additionalProperties":{"type":"string","format":"uuid"},"type":"object"},{"type":"null"}],"title":"Class Map"},"confidence_threshold":{"type":"number","maximum":1.0,"minimum":0.0,"title":"Confidence Threshold","default":0.5},"labeler_assist_enabled":{"type":"boolean","title":"Labeler Assist Enabled","default":false},"auto_advance_all_high_conf":{"type":"boolean","title":"Auto Advance All High Conf","default":false}},"type":"object","required":["model_ref"],"title":"AutoLabelConfig","description":"Per-project auto-label policy (manager-owned). `class_map` maps the model's\nclass id (string key) → a project class id; omit it to auto-derive when the\nmodel was trained from this project's version (names match 1:1)."},"AutoLabelConfigResponse":{"properties":{"config":{"anyOf":[{"$ref":"#/components/schemas/AutoLabelConfig"},{"type":"null"}]},"ready":{"type":"boolean","title":"Ready"},"ready_reason":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Ready Reason"}},"type":"object","required":["config","ready"],"title":"AutoLabelConfigResponse"},"AutoLabelRunRequest":{"properties":{"asset_ids":{"anyOf":[{"items":{"type":"string","format":"uuid"},"type":"array"},{"type":"null"}],"title":"Asset Ids"},"all":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"All"},"confidence_threshold":{"anyOf":[{"type":"number","maximum":1.0,"minimum":0.0},{"type":"null"}],"title":"Confidence Threshold"},"skip_annotated":{"type":"boolean","title":"Skip Annotated","default":true},"on_existing":{"type":"string","enum":["overwrite","append"],"title":"On Existing","default":"append"},"on_overlap":{"type":"string","enum":["respect","replace"],"title":"On Overlap","default":"respect"}},"type":"object","title":"AutoLabelRunRequest","description":"Start a batch pre-label run. Exactly one of `asset_ids` / `all` (`all` = every\nasset in the project).\n\nExisting-annotation policy is a single 3-level funnel (ADR-0075 rev 2) — easy to\nreason about, and **human work is always protected**: a bulk run NEVER deletes or\nreplaces a human-made (manual/imported) annotation; only AI-generated (`model`)\nannotations are ever cleared.\n\n 1. `skip_annotated` (default true) — only label assets with zero annotations.\n The safe \"pre-label the untouched pile\". If false, drill into 2.\n 2. `on_existing` — for assets that already have annotations:\n • \"overwrite\" → clear the asset's AI labels and write fresh AI (human kept);\n the \"I retrained, refresh the un-reviewed pile\" case.\n • \"append\" (default) → keep everything, add new AI, per 3.\n 3. `on_overlap` — when an appended prediction overlaps an existing annotation:\n • \"respect\" (default) → drop the prediction (fill only the gaps that a\n human/AI annotation didn't already cover).\n • \"replace\" → the prediction wins over an overlapping *AI* annotation\n (deletes it); over a *human* annotation it always yields (human protected).\n\n`on_existing` / `on_overlap` are ignored when `skip_annotated` is true. Overlap is\nIoU-gated on bounding boxes (polygons by their bounds — a v1 approximation)."},"AutoLabelSuggestRequest":{"properties":{"confidence":{"anyOf":[{"type":"number","maximum":1.0,"minimum":0.0},{"type":"null"}],"title":"Confidence"}},"type":"object","title":"AutoLabelSuggestRequest","description":"In-editor assist for one image — runs the project's configured trained-model\ndeployment. Body is optional; `confidence` overrides the project threshold for\nthis call only."},"AutoLabelSuggestResponse":{"properties":{"suggestions":{"items":{"$ref":"#/components/schemas/AutoLabelSuggestion"},"type":"array","title":"Suggestions"},"inference_ms":{"type":"integer","title":"Inference Ms"}},"type":"object","required":["suggestions","inference_ms"],"title":"AutoLabelSuggestResponse"},"AutoLabelSuggestion":{"properties":{"class_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Class Id"},"class_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Class Name"},"type":{"$ref":"#/components/schemas/OutputGeometry"},"geometry":{"oneOf":[{"$ref":"#/components/schemas/BboxGeometry"},{"$ref":"#/components/schemas/PolygonGeometry"},{"$ref":"#/components/schemas/KeypointGeometry"}],"title":"Geometry","discriminator":{"propertyName":"type","mapping":{"bbox":"#/components/schemas/BboxGeometry","keypoint":"#/components/schemas/KeypointGeometry","polygon":"#/components/schemas/PolygonGeometry"}}},"confidence":{"type":"number","title":"Confidence"}},"type":"object","required":["class_id","class_name","type","geometry","confidence"],"title":"AutoLabelSuggestion","description":"One predicted object. `class_id` is null when the model class couldn't be\nmapped to a project class (FE prompts the labeler to assign before commit)."},"BboxGeometry":{"properties":{"type":{"type":"string","const":"bbox","title":"Type","default":"bbox"},"x":{"type":"number","title":"X"},"y":{"type":"number","title":"Y"},"w":{"type":"number","exclusiveMinimum":0.0,"title":"W"},"h":{"type":"number","exclusiveMinimum":0.0,"title":"H"}},"type":"object","required":["x","y","w","h"],"title":"BboxGeometry"},"BboxGeometryRead":{"properties":{"type":{"type":"string","const":"bbox","title":"Type","default":"bbox"},"x":{"type":"number","title":"X"},"y":{"type":"number","title":"Y"},"w":{"type":"number","title":"W"},"h":{"type":"number","title":"H"}},"additionalProperties":true,"type":"object","required":["x","y","w","h"],"title":"BboxGeometryRead"},"BoxAnchor":{"properties":{"x":{"type":"number","maximum":2.0,"minimum":-1.0,"title":"X"},"y":{"type":"number","maximum":2.0,"minimum":-1.0,"title":"Y"},"w":{"type":"number","maximum":3.0,"exclusiveMinimum":0.0,"title":"W"},"h":{"type":"number","maximum":3.0,"exclusiveMinimum":0.0,"title":"H"}},"type":"object","required":["x","y","w","h"],"title":"BoxAnchor"},"BoxPrompt":{"properties":{"box":{"items":{"type":"number"},"type":"array","maxItems":4,"minItems":4,"title":"Box","description":"[x1,y1,x2,y2] pixels on ref_image."},"ref_image":{"anyOf":[{"$ref":"#/components/schemas/ImageRef"},{"type":"null"}],"description":"Image the box is drawn on; defaults to the search image when omitted."},"prompt":{"type":"string","title":"Prompt","description":"Free-text prompt naming the matches (ZERO needs a non-empty text).","default":"object"}},"type":"object","required":["box"],"title":"BoxPrompt","description":"A visual (few-shot) prompt: 'find things like the object in this box'.\n`ref_image` defaults to the search image when omitted (box drawn on it directly)."},"BulkOpRequest":{"properties":{"asset_ids":{"anyOf":[{"items":{"type":"string","format":"uuid"},"type":"array"},{"type":"null"}],"title":"Asset Ids"},"filter":{"anyOf":[{"$ref":"#/components/schemas/AssetFilter"},{"type":"null"}]},"exclude_asset_ids":{"anyOf":[{"items":{"type":"string","format":"uuid"},"type":"array"},{"type":"null"}],"title":"Exclude Asset Ids"}},"type":"object","title":"BulkOpRequest","description":"Common shape for bulk-delete. Exactly one of `asset_ids` or `filter`\nmust be present. `exclude_asset_ids` (filter-mode only) deselects rows\nfrom the matched set so the FE's \"select all matching except these few\"\nflow doesn't accidentally delete a deselected asset."},"CallbackRequest":{"properties":{"code":{"type":"string","minLength":1,"title":"Code","description":"One-time authorization code from the AuthKit redirect."}},"type":"object","required":["code"],"title":"CallbackRequest","description":"Body for POST /auth/callback — the `code` WorkOS appended to the redirect."},"CapabilityDescriptor":{"properties":{"task":{"$ref":"#/components/schemas/TrainingTask","description":"Training task the deployed model performs."},"output":{"$ref":"#/components/schemas/OutputGeometry","description":"Geometry kind the model emits."},"class_map":{"items":{"$ref":"#/components/schemas/DeploymentLabel"},"type":"array","title":"Class Map","description":"The model's class list."},"image_sources":{"items":{"type":"string"},"type":"array","title":"Image Sources","description":"Accepted image input sources for predict.","default":["image_b64"]},"params":{"additionalProperties":{"$ref":"#/components/schemas/ParamSpec"},"type":"object","title":"Params","description":"Tunable predict parameters (confidence, iou, max_detections) with bounds and defaults from the model's validation-split sweep — the operational serving defaults (see ParamSpec.default for why these differ from the scorecard's test-split `optimal_confidence`)."}},"type":"object","required":["task","output","class_map","params"],"title":"CapabilityDescriptor","description":"Task-keyed contract the FE renders the playground from (ADR-0067)."},"CatalogEarlyStoppingKnob":{"properties":{"default":{"type":"boolean","title":"Default","description":"Whether early stopping is on when the run omits it."}},"type":"object","required":["default"],"title":"CatalogEarlyStoppingKnob"},"CatalogEpochsKnob":{"properties":{"default":{"type":"integer","title":"Default","description":"Default epoch count when the run omits `epochs`."},"min":{"type":"integer","title":"Min","description":"Lowest accepted `epochs` value."},"max":{"type":"integer","title":"Max","description":"Highest accepted `epochs` value."}},"type":"object","required":["default","min","max"],"title":"CatalogEpochsKnob"},"CatalogModelResponse":{"properties":{"key":{"type":"string","title":"Key","description":"Catalog model key (selection id, e.g. 'rf-detr-nano')."},"name":{"type":"string","title":"Name","description":"Display name (e.g. 'RF-DETR Nano')."},"task":{"$ref":"#/components/schemas/TrainingTask","description":"Task the variant trains for (detection or segmentation)."},"options":{"$ref":"#/components/schemas/CatalogOptions","description":"Configurable-knob schema: defaults and bounds for epochs / early_stopping / patience / split."}},"type":"object","required":["key","name","task","options"],"title":"CatalogModelResponse"},"CatalogOptions":{"properties":{"epochs":{"$ref":"#/components/schemas/CatalogEpochsKnob"},"early_stopping":{"$ref":"#/components/schemas/CatalogEarlyStoppingKnob"},"patience":{"$ref":"#/components/schemas/CatalogPatienceKnob"},"split":{"$ref":"#/components/schemas/CatalogSplitKnob"}},"type":"object","required":["epochs","early_stopping","patience","split"],"title":"CatalogOptions"},"CatalogPatienceKnob":{"properties":{"default":{"type":"integer","title":"Default","description":"Default patience (epochs without val-mAP improvement)."},"min":{"type":"integer","title":"Min","description":"Lowest accepted `patience` value."},"max":{"anyOf":[{"type":"integer"},{"type":"string","const":"epochs"}],"title":"Max","description":"Highest accepted `patience`; the literal string 'epochs' means the bound is dynamic — the run's own chosen epoch count."}},"type":"object","required":["default","min","max"],"title":"CatalogPatienceKnob"},"CatalogSplitDefault":{"properties":{"strategy":{"$ref":"#/components/schemas/SplitStrategy","description":"Default split strategy."},"train":{"type":"number","title":"Train","description":"Default train fraction."},"val":{"type":"number","title":"Val","description":"Default val fraction."},"test":{"type":"number","title":"Test","description":"Default test fraction."},"seed":{"type":"integer","title":"Seed","description":"Default split seed."}},"type":"object","required":["strategy","train","val","test","seed"],"title":"CatalogSplitDefault"},"CatalogSplitKnob":{"properties":{"default":{"$ref":"#/components/schemas/CatalogSplitDefault","description":"Split applied when the run omits one."},"strategies":{"items":{"$ref":"#/components/schemas/SplitStrategy"},"type":"array","title":"Strategies","description":"Accepted split strategies."}},"type":"object","required":["default","strategies"],"title":"CatalogSplitKnob"},"ChangePasswordRequest":{"properties":{"previous_password":{"type":"string","minLength":1,"title":"Previous Password","description":"Your current password (re-verified)."},"new_password":{"type":"string","maxLength":256,"minLength":10,"title":"New Password","description":"The new password (10+ characters, not a common one)."}},"type":"object","required":["previous_password","new_password"],"title":"ChangePasswordRequest","description":"Change your own password. `previous_password` is re-verified against the\nidentity provider before the new one is set; `new_password` must satisfy the\nprovider's strength policy (10+ chars, not a common password)."},"ClassCountById":{"properties":{"name":{"type":"string","title":"Name"},"count":{"type":"integer","title":"Count"},"id":{"anyOf":[{"type":"integer"},{"type":"string"}],"title":"Id"}},"type":"object","required":["name","count","id"],"title":"ClassCountById","description":"COCO/Delta per-class entry (id-keyed). COCO id is int; delta id is the\nclass UUID stringified — hence `int | str`."},"ClassCountByIdx":{"properties":{"name":{"type":"string","title":"Name"},"count":{"type":"integer","title":"Count"},"idx":{"type":"integer","title":"Idx"}},"type":"object","required":["name","count","idx"],"title":"ClassCountByIdx","description":"YOLO per-class entry (0-based dataset index)."},"ClassCreate":{"properties":{"kind":{"type":"string","const":"object","title":"Kind","default":"object"},"name":{"type":"string","maxLength":255,"minLength":1,"title":"Name"},"color":{"type":"string","pattern":"^#[0-9A-Fa-f]{6}$","title":"Color","default":"#888888"},"allowed_types":{"items":{"type":"string"},"type":"array","minItems":1,"title":"Allowed Types"},"keypoint_topology":{"anyOf":[{"$ref":"#/components/schemas/KeypointTopology"},{"type":"null"}]},"attributes":{"items":{"$ref":"#/components/schemas/AttributeCreate"},"type":"array","title":"Attributes"}},"type":"object","required":["name","allowed_types"],"title":"ClassCreate","description":"Create an object class (`kind='object'`) with optional inline attributes\n(fanned out to owned classification rows in one transaction)."},"ClassKind":{"type":"string","enum":["object","classification"],"title":"ClassKind"},"ClassRef":{"properties":{"id":{"type":"string","format":"uuid","title":"Id"},"kind":{"$ref":"#/components/schemas/ClassKind"},"name":{"type":"string","title":"Name"},"color":{"type":"string","title":"Color"},"owner_class_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Owner Class Id"},"allowed_types":{"items":{"type":"string"},"type":"array","title":"Allowed Types"},"archived":{"type":"boolean","title":"Archived"},"keypoint_topology":{"anyOf":[{"$ref":"#/components/schemas/KeypointTopology"},{"type":"null"}]},"classification_config":{"anyOf":[{"$ref":"#/components/schemas/ClassificationConfig"},{"type":"null"}]}},"type":"object","required":["id","kind","name","color","allowed_types","archived"],"title":"ClassRef","description":"Flat serialization of a single class row (any kind) — used to embed the class an\nannotation is an instance of (annotation list). Distinct from the nested ClassResponse,\nwhich carries an object class's attributes for the editor."},"ClassResponse":{"properties":{"id":{"type":"string","format":"uuid","title":"Id"},"kind":{"$ref":"#/components/schemas/ClassKind"},"name":{"type":"string","title":"Name"},"color":{"type":"string","title":"Color"},"allowed_types":{"items":{"type":"string"},"type":"array","title":"Allowed Types"},"archived":{"type":"boolean","title":"Archived"},"keypoint_topology":{"anyOf":[{"$ref":"#/components/schemas/KeypointTopology"},{"type":"null"}]},"attributes":{"items":{"$ref":"#/components/schemas/AttributeResponse"},"type":"array","title":"Attributes"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"updated_at":{"type":"string","format":"date-time","title":"Updated At"}},"type":"object","required":["id","kind","name","color","allowed_types","archived","created_at","updated_at"],"title":"ClassResponse","description":"An object class with its owned attributes inline (ADR-0090 §3, Option A)."},"ClassUpdate":{"properties":{"name":{"anyOf":[{"type":"string","maxLength":255,"minLength":1},{"type":"null"}],"title":"Name"},"color":{"anyOf":[{"type":"string","pattern":"^#[0-9A-Fa-f]{6}$"},{"type":"null"}],"title":"Color"},"allowed_types":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Allowed Types"},"archived":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Archived"},"classification_config":{"anyOf":[{"$ref":"#/components/schemas/ClassificationConfig"},{"type":"null"}]}},"type":"object","title":"ClassUpdate","description":"Edit a class's OWN fields (attribute edits go through the attribute endpoints).\nWorks for object classes (name/color/allowed_types/archived) and image-level\nclassifications (name/color/archived + the `classification_config` policy fields,\nADR-0085). `keypoint_topology` stays immutable. The handler enforces kind-appropriate\nfields."},"ClassesResponse":{"properties":{"classes":{"items":{"$ref":"#/components/schemas/ClassResponse"},"type":"array","title":"Classes"},"image_classifications":{"items":{"$ref":"#/components/schemas/ImageClassificationResponse"},"type":"array","title":"Image Classifications"}},"type":"object","required":["classes","image_classifications"],"title":"ClassesResponse","description":"Composed classes read (ADR-0090 §3, Option A). Deliberately NOT cursor-paginated\n(ADR-0012 exception): a project's class set is bounded + small (ADR-0078 caps 1000)\nand the nested tree must be assembled whole for the FE editor."},"ClassificationConfig":{"properties":{"version":{"type":"integer","minimum":1.0,"title":"Version","default":1},"input_type":{"$ref":"#/components/schemas/ClassificationInputType"},"options":{"items":{"$ref":"#/components/schemas/ClassificationOption"},"type":"array","title":"Options"},"default":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Default"},"required":{"type":"boolean","title":"Required","default":false}},"type":"object","required":["input_type"],"title":"ClassificationConfig","description":"Per-label config for `type=classification` annotations. Stored on\n`ProjectClass.classification_config` as JSONB. ADR-0031.\n\n`options` is required for `radio` / `checkbox`, ignored for `text`.\n`default` (optional) names the pre-selected value (radio/text) or single\npre-selected option (checkbox). `required: true` makes the workflow\n`submit` transition refuse if no annotation for this label exists on\nthe asset."},"ClassificationInputType":{"type":"string","enum":["radio","checkbox","text"],"title":"ClassificationInputType"},"ClassificationOption":{"properties":{"value":{"type":"string","maxLength":128,"minLength":1,"title":"Value"},"display":{"type":"string","maxLength":255,"minLength":1,"title":"Display"}},"type":"object","required":["value","display"],"title":"ClassificationOption"},"ClassificationSummary":{"properties":{"class_id":{"type":"string","format":"uuid","title":"Class Id","description":"Image-classification class id."},"name":{"type":"string","title":"Name","description":"Class display name."},"value":{"anyOf":[{"type":"string"},{"items":{"type":"string"},"type":"array"}],"title":"Value","description":"The stored answer (`data.answer`): a string (radio/text) or a list (checkbox)."}},"type":"object","required":["class_id","name","value"],"title":"ClassificationSummary","description":"One answered image-level classification on an asset (asset-list summary)."},"CocoResultDetails":{"properties":{"format":{"type":"string","const":"coco","title":"Format"},"num_images":{"type":"integer","title":"Num Images"},"num_annotations":{"type":"integer","title":"Num Annotations"},"num_categories":{"type":"integer","title":"Num Categories"},"classes":{"items":{"$ref":"#/components/schemas/ClassCountById"},"type":"array","title":"Classes"},"classes_truncated":{"type":"boolean","title":"Classes Truncated","default":false},"classes_total":{"type":"integer","title":"Classes Total","default":0}},"type":"object","required":["format","num_images","num_annotations","num_categories"],"title":"CocoResultDetails","description":"`result_details` for a COCO bundle. `classes`/`classes_truncated`/\n`classes_total` were added when `_top_classes` landed — Optional-with-default\nso a pre-that-change row still serializes."},"CommentCreateRequest":{"properties":{"anchor_kind":{"type":"string","enum":["asset","point","box","annotation","point3d","cuboid3d"],"title":"Anchor Kind","default":"asset"},"anchor_coords":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Anchor Coords"},"annotation_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Annotation Id"},"parent_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Parent Id"},"body":{"type":"string","maxLength":10000,"minLength":1,"title":"Body"}},"type":"object","required":["body"],"title":"CommentCreateRequest","description":"POST /projects/{pid}/assets/{aid}/comments.\n\nAnchor is one-of:\n - kind=asset: no coords, no annotation_id\n - kind=point: coords={\"x\", \"y\"}\n - kind=box: coords={\"x\", \"y\", \"w\", \"h\"}\n - kind=annotation: annotation_id required\nReplies: set parent_id (and inherit anchor from the root server-side)."},"CommentListPage":{"properties":{"items":{"items":{"$ref":"#/components/schemas/CommentResponse"},"type":"array","title":"Items"},"next_cursor":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Next Cursor"},"total":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Total"}},"type":"object","required":["items"],"title":"CommentListPage"},"CommentOrigin":{"type":"string","enum":["user","workflow_reject"],"title":"CommentOrigin"},"CommentResponse":{"properties":{"id":{"type":"string","format":"uuid","title":"Id"},"project_id":{"type":"string","format":"uuid","title":"Project Id"},"asset_id":{"type":"string","format":"uuid","title":"Asset Id"},"asset_kind":{"$ref":"#/components/schemas/DatasetKind"},"anchor_kind":{"$ref":"#/components/schemas/AnchorKind"},"anchor_coords":{"anyOf":[{"$ref":"#/components/schemas/PointAnchor"},{"$ref":"#/components/schemas/BoxAnchor"},{"type":"null"}],"title":"Anchor Coords"},"annotation_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Annotation Id"},"parent_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Parent Id"},"body":{"type":"string","title":"Body"},"author_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Author Id"},"status":{"$ref":"#/components/schemas/CommentStatus"},"origin":{"$ref":"#/components/schemas/CommentOrigin"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"updated_at":{"type":"string","format":"date-time","title":"Updated At"},"seq":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Seq"},"reply_count":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Reply Count"}},"type":"object","required":["id","project_id","asset_id","asset_kind","anchor_kind","annotation_id","parent_id","body","author_id","status","origin","created_at","updated_at"],"title":"CommentResponse"},"CommentStatus":{"type":"string","enum":["open","resolved"],"title":"CommentStatus"},"CommentUpdateRequest":{"properties":{"body":{"anyOf":[{"type":"string","maxLength":10000,"minLength":1},{"type":"null"}],"title":"Body"},"anchor_kind":{"anyOf":[{"type":"string","enum":["asset","point","box","annotation","point3d","cuboid3d"]},{"type":"null"}],"title":"Anchor Kind"},"anchor_coords":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Anchor Coords"},"annotation_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Annotation Id"},"frame_number":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Frame Number"}},"type":"object","title":"CommentUpdateRequest","description":"PATCH /comments/{id}. Body and/or anchor edit (root comments only).\n\nSend only what changes. If *any* anchor field is present, the whole anchor is\nreplaced and re-validated as a unit — so you can't end up `kind=box` with a\nstale `point` coord. Anchor edits are rejected on replies (route-level)."},"ConfusionMatrix":{"properties":{"labels":{"items":{"type":"string"},"type":"array","title":"Labels"},"matrix":{"items":{"items":{"type":"integer"},"type":"array"},"type":"array","title":"Matrix"}},"additionalProperties":false,"type":"object","required":["labels","matrix"],"title":"ConfusionMatrix","description":"K classes + a trailing 'background' bucket for FP/missed."},"DatasetCreate":{"properties":{"name":{"type":"string","maxLength":255,"minLength":1,"title":"Name"},"description":{"anyOf":[{"type":"string","maxLength":1000},{"type":"null"}],"title":"Description"}},"type":"object","required":["name"],"title":"DatasetCreate"},"DatasetKind":{"type":"string","enum":["image","video"],"title":"DatasetKind"},"DatasetResponse":{"properties":{"id":{"type":"string","format":"uuid","title":"Id"},"name":{"type":"string","title":"Name"},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description"},"created_by":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Created By"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"updated_at":{"type":"string","format":"date-time","title":"Updated At"}},"type":"object","required":["id","name","description","created_by","created_at","updated_at"],"title":"DatasetResponse"},"DatasetStatsResponse":{"properties":{"total":{"type":"integer","title":"Total"},"by_status":{"additionalProperties":{"type":"integer"},"propertyNames":{"$ref":"#/components/schemas/AssetStatus"},"type":"object","title":"By Status"},"embedding":{"anyOf":[{"additionalProperties":{"type":"integer"},"propertyNames":{"$ref":"#/components/schemas/EmbeddingStatus"},"type":"object"},{"type":"null"}],"title":"Embedding"}},"type":"object","required":["total","by_status"],"title":"DatasetStatsResponse","description":"Total counts for the FE asset grid header (FE feedback 2026-05-07).\nSeparate endpoint per ADR-0012 (cursor pagination, no totals on list).\n\n`embedding`: semantic-search coverage so the FE can show\n\"1872 / 2000 embedded\" badges and prompt the user to re-embed when there\nare pending arrivals."},"DatasetUpdate":{"properties":{"name":{"anyOf":[{"type":"string","maxLength":255,"minLength":1},{"type":"null"}],"title":"Name"},"description":{"anyOf":[{"type":"string","maxLength":1000},{"type":"null"}],"title":"Description"}},"type":"object","title":"DatasetUpdate","description":"PATCH semantics: omit a field to leave it unchanged. Explicit `null` is\ntreated the same as omit (use `\"\"` to clear `description`). Future revision\nmay switch to JSON Merge Patch with explicit-null = clear semantics if FE asks."},"DeltaResultDetails":{"properties":{"format":{"type":"string","const":"delta","title":"Format"},"schema_version":{"type":"string","title":"Schema Version"},"num_assets":{"type":"integer","title":"Num Assets"},"num_annotations":{"type":"integer","title":"Num Annotations"},"annotations_by_type":{"additionalProperties":{"type":"integer"},"type":"object","title":"Annotations By Type"},"classes":{"items":{"$ref":"#/components/schemas/ClassCountById"},"type":"array","title":"Classes"},"classes_truncated":{"type":"boolean","title":"Classes Truncated","default":false},"classes_total":{"type":"integer","title":"Classes Total","default":0}},"type":"object","required":["format","schema_version","num_assets","num_annotations"],"title":"DeltaResultDetails","description":"`annotations_by_type` is genuinely open-ended by annotation type — the one\nlegit dict inside an otherwise-fixed model."},"DeploymentCreate":{"properties":{},"type":"object","title":"DeploymentCreate"},"DeploymentDetail":{"properties":{"id":{"type":"string","format":"uuid","title":"Id","description":"Deployment identifier."},"model_id":{"type":"string","format":"uuid","title":"Model Id","description":"Trained model backing this deployment."},"project_id":{"type":"string","format":"uuid","title":"Project Id","description":"Project the deployment belongs to."},"name":{"type":"string","title":"Name","description":"The model's name, for display."},"task":{"$ref":"#/components/schemas/TrainingTask","description":"Training task the deployed model performs."},"status":{"$ref":"#/components/schemas/DeploymentStatus","description":"Lifecycle state: deploying (warming), ready (predict is fast), stopped/failed (terminal)."},"gpu":{"type":"string","title":"Gpu","description":"Server-set MME instance type (informational)."},"last_activity_at":{"type":"string","format":"date-time","title":"Last Activity At","description":"Informational 'last used' timestamp; no time-based teardown."},"error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error","description":"Failure detail when status is failed, else null."},"created_by":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Created By","description":"User who created the deployment, if known."},"created_at":{"type":"string","format":"date-time","title":"Created At","description":"When the deployment was created."},"capability":{"anyOf":[{"$ref":"#/components/schemas/CapabilityDescriptor"},{"type":"null"}],"description":"Playground capability contract; present once status is ready."}},"type":"object","required":["id","model_id","project_id","name","task","status","gpu","last_activity_at","error","created_by","created_at"],"title":"DeploymentDetail"},"DeploymentLabel":{"properties":{"class_id":{"type":"integer","title":"Class Id","description":"0-indexed model class."},"name":{"type":"string","title":"Name","description":"Human-readable class name."},"color":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Color","description":"Display color for the class, if set."}},"type":"object","required":["class_id","name"],"title":"DeploymentLabel"},"DeploymentPage":{"properties":{"items":{"items":{"$ref":"#/components/schemas/DeploymentResponse"},"type":"array","title":"Items","description":"The deployments on this page."},"next_cursor":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Next Cursor","description":"Opaque cursor for the next page; null when this is the last page."}},"type":"object","required":["items"],"title":"DeploymentPage"},"DeploymentResponse":{"properties":{"id":{"type":"string","format":"uuid","title":"Id","description":"Deployment identifier."},"model_id":{"type":"string","format":"uuid","title":"Model Id","description":"Trained model backing this deployment."},"project_id":{"type":"string","format":"uuid","title":"Project Id","description":"Project the deployment belongs to."},"name":{"type":"string","title":"Name","description":"The model's name, for display."},"task":{"$ref":"#/components/schemas/TrainingTask","description":"Training task the deployed model performs."},"status":{"$ref":"#/components/schemas/DeploymentStatus","description":"Lifecycle state: deploying (warming), ready (predict is fast), stopped/failed (terminal)."},"gpu":{"type":"string","title":"Gpu","description":"Server-set MME instance type (informational)."},"last_activity_at":{"type":"string","format":"date-time","title":"Last Activity At","description":"Informational 'last used' timestamp; no time-based teardown."},"error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error","description":"Failure detail when status is failed, else null."},"created_by":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Created By","description":"User who created the deployment, if known."},"created_at":{"type":"string","format":"date-time","title":"Created At","description":"When the deployment was created."}},"type":"object","required":["id","model_id","project_id","name","task","status","gpu","last_activity_at","error","created_by","created_at"],"title":"DeploymentResponse"},"DeploymentStatus":{"type":"string","enum":["deploying","ready","stopped","failed"],"title":"DeploymentStatus"},"EmbeddingStatus":{"type":"string","enum":["pending","ready","failed"],"title":"EmbeddingStatus"},"ExportCreateRequest":{"properties":{"format":{"$ref":"#/components/schemas/ExportFormat","description":"Export bundle format: coco, yolo, or delta."},"include_assets":{"type":"boolean","title":"Include Assets","description":"Whether to include the source asset files in the export bundle.","default":true},"exclude_empty":{"type":"boolean","title":"Exclude Empty","description":"If true, skip assets that have no annotations from the export.","default":false}},"type":"object","required":["format"],"title":"ExportCreateRequest","description":"POST body. All fields optional except `format`.\n\nPydantic defaults to ignoring unknown fields (`extra='ignore'`), so callers\nthat still send the deprecated `splits` / `split_seed` / `stratify_by`\nfields from the v1 contract get a silent no-op. The export is always\nunsplit regardless."},"ExportDownloadResponse":{"properties":{"url":{"type":"string","title":"Url","description":"Pre-signed HTTPS URL to download the export bundle; valid for 1 hour."}},"type":"object","required":["url"],"title":"ExportDownloadResponse","description":"Response from `/exports/{eid}/download` when ready.\n\nCaller follows `url` with a normal HTTPS GET. S3 validates expiry at\nrequest start so 1h TTL covers arbitrary-duration downloads."},"ExportFormat":{"type":"string","enum":["coco","yolo","delta"],"title":"ExportFormat"},"ExportListPage":{"properties":{"items":{"items":{"$ref":"#/components/schemas/ExportResponse"},"type":"array","title":"Items"},"next_cursor":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Next Cursor"}},"type":"object","required":["items"],"title":"ExportListPage"},"ExportResponse":{"properties":{"id":{"type":"string","format":"uuid","title":"Id","description":"Unique identifier of this export."},"project_id":{"type":"string","format":"uuid","title":"Project Id","description":"Identifier of the project this export belongs to."},"format":{"$ref":"#/components/schemas/ExportFormat","description":"Export bundle format: coco, yolo, or delta."},"status":{"$ref":"#/components/schemas/ExportStatus","description":"Current status of the export."},"size_bytes":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Size Bytes","description":"Size of the produced export bundle in bytes; null until complete."},"exported_count":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Exported Count","description":"Number of assets successfully exported; null until complete."},"skipped_count":{"type":"integer","title":"Skipped Count","description":"Number of assets skipped during export."},"skipped_details":{"anyOf":[{"items":{"$ref":"#/components/schemas/SkippedDetail"},"type":"array"},{"type":"null"}],"title":"Skipped Details","description":"Details of skipped assets; null when nothing was skipped."},"error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error","description":"Error message if the export failed; null otherwise."},"job_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Job Id","description":"Back-link to the worker-dispatch Job row; poll /jobs/{job_id} for progress."},"result_details":{"anyOf":[{"oneOf":[{"$ref":"#/components/schemas/CocoResultDetails"},{"$ref":"#/components/schemas/YoloResultDetails"},{"$ref":"#/components/schemas/DeltaResultDetails"}],"discriminator":{"propertyName":"format","mapping":{"coco":"#/components/schemas/CocoResultDetails","delta":"#/components/schemas/DeltaResultDetails","yolo":"#/components/schemas/YoloResultDetails"}}},{"type":"null"}],"title":"Result Details","description":"Format-specific output stats, populated when status is terminal."},"created_by":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Created By","description":"Identifier of the user who requested the export."},"created_at":{"type":"string","format":"date-time","title":"Created At","description":"Timestamp when the export was created."},"completed_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Completed At","description":"Timestamp when the export reached a terminal status; null while in progress."}},"type":"object","required":["id","project_id","format","status","size_bytes","exported_count","skipped_count","skipped_details","error","job_id","created_by","created_at","completed_at"],"title":"ExportResponse"},"ExportStatus":{"type":"string","enum":["pending","queued","running","ready","failed","expired"],"title":"ExportStatus"},"FoundationModelPage":{"properties":{"items":{"items":{"$ref":"#/components/schemas/FoundationModelResponse"},"type":"array","title":"Items"},"next_cursor":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Next Cursor"}},"type":"object","required":["items"],"title":"FoundationModelPage","description":"ADR-0012 pagination envelope. The catalog is a static bounded set, so it always\nfits one page and `next_cursor` is always None — the shape is kept uniform with the\nother list endpoints so clients can page-loop without special-casing."},"FoundationModelResponse":{"properties":{"key":{"type":"string","title":"Key"},"name":{"type":"string","title":"Name"},"description":{"type":"string","title":"Description"},"task":{"type":"string","const":"detection","title":"Task"},"output":{"type":"string","const":"bbox","title":"Output"},"prompt_modes":{"items":{"type":"string"},"type":"array","title":"Prompt Modes","description":"Supported prompt kinds (e.g. text, box) for this model."},"image_sources":{"items":{"type":"string"},"type":"array","title":"Image Sources","description":"Accepted image input sources for predict."},"params":{"additionalProperties":{"$ref":"#/components/schemas/ParamSpec"},"type":"object","title":"Params","description":"Tunable predict parameters (confidence) with bounds and defaults."}},"type":"object","required":["key","name","description","task","output","prompt_modes","image_sources","params"],"title":"FoundationModelResponse","description":"One catalog entry (the playground's model picker + control schema)."},"FoundationModelStatus":{"properties":{"key":{"type":"string","title":"Key"},"name":{"type":"string","title":"Name"},"description":{"type":"string","title":"Description"},"task":{"type":"string","const":"detection","title":"Task"},"output":{"type":"string","const":"bbox","title":"Output"},"prompt_modes":{"items":{"type":"string"},"type":"array","title":"Prompt Modes","description":"Supported prompt kinds (e.g. text, box) for this model."},"image_sources":{"items":{"type":"string"},"type":"array","title":"Image Sources","description":"Accepted image input sources for predict."},"params":{"additionalProperties":{"$ref":"#/components/schemas/ParamSpec"},"type":"object","title":"Params","description":"Tunable predict parameters (confidence) with bounds and defaults."},"status":{"type":"string","enum":["ready","warming","unavailable"],"title":"Status"}},"type":"object","required":["key","name","description","task","output","prompt_modes","image_sources","params","status"],"title":"FoundationModelStatus","description":"Catalog entry + live availability. `status`:\n- ready → endpoint InService, predict works\n- warming → endpoint creating (just brought up; ~3-8 min to ready)\n- unavailable → endpoint down (off-hours; predict returns 503)"},"FoundationPrediction":{"properties":{"type":{"type":"string","const":"bbox","title":"Type","description":"Geometry kind of this prediction."},"class_name":{"type":"string","title":"Class Name","description":"Predicted class name for the detection."},"geometry":{"oneOf":[{"$ref":"#/components/schemas/BboxGeometry"},{"$ref":"#/components/schemas/PolygonGeometry"},{"$ref":"#/components/schemas/KeypointGeometry"}],"title":"Geometry","description":"Typed geometry (bbox) — the same discriminated shape as annotation.geometry.","discriminator":{"propertyName":"type","mapping":{"bbox":"#/components/schemas/BboxGeometry","keypoint":"#/components/schemas/KeypointGeometry","polygon":"#/components/schemas/PolygonGeometry"}}},"confidence":{"type":"number","title":"Confidence","description":"Model confidence for this prediction."}},"type":"object","required":["type","class_name","geometry","confidence"],"title":"FoundationPrediction"},"FrozenClass":{"properties":{"id":{"type":"string","format":"uuid","title":"Id"},"name":{"type":"string","title":"Name"},"color":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Color"},"allowed_types":{"items":{"type":"string"},"type":"array","title":"Allowed Types"},"keypoint_topology":{"anyOf":[{"$ref":"#/components/schemas/KeypointTopology"},{"type":"null"}]},"archived":{"type":"boolean","title":"Archived","default":false}},"type":"object","required":["id","name"],"title":"FrozenClass","description":"One class as frozen into a `ProjectVersion.frozen_classes` snapshot at\nversion-create time. Mirrors the writer at `routes/projects.py` create_version.\n\n`keypoint_topology` is typed as the same `KeypointTopology` the write-to-class\npath validates (ADR-0104): the frozen copy is that validated JSONB verbatim,\nand the shape hasn't changed since ADR-0030, so re-declaring it costs nothing\nand gives the SDK the real type. `classification_config` is deliberately NOT\nfrozen. Post-migration-0075 the inner key is `classes` (renamed from the\nlegacy `labels`, ADR-0097), so all live rows are uniform."},"FrozenClassSet":{"properties":{"classes":{"items":{"$ref":"#/components/schemas/FrozenClass"},"type":"array","title":"Classes"}},"type":"object","title":"FrozenClassSet","description":"The `ProjectVersion.frozen_classes` blob: `{\"classes\": [FrozenClass, ...]}`.\nTyped on the wire (`VersionResponse.frozen_classes`) — rows are uniform\npost-0075. Storage stays JSON (`.model_dump(mode=\"json\")` into JSONB)."},"FrozenShape":{"properties":{"id":{"type":"string","format":"uuid","title":"Id"},"class_id":{"type":"string","format":"uuid","title":"Class Id"},"type":{"$ref":"#/components/schemas/AnnotationType"},"parent_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Parent Id"},"instance_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Instance Id"},"geometry":{"anyOf":[{"oneOf":[{"$ref":"#/components/schemas/BboxGeometryRead"},{"$ref":"#/components/schemas/PolygonGeometryRead"},{"$ref":"#/components/schemas/KeypointGeometryRead"}],"discriminator":{"propertyName":"type","mapping":{"bbox":"#/components/schemas/BboxGeometryRead","keypoint":"#/components/schemas/KeypointGeometryRead","polygon":"#/components/schemas/PolygonGeometryRead"}}},{"type":"null"}],"title":"Geometry"},"data":{"additionalProperties":true,"type":"object","title":"Data"},"source":{"$ref":"#/components/schemas/AnnotationSource","default":"manual"},"created_by":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Created By"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"updated_at":{"type":"string","format":"date-time","title":"Updated At"}},"type":"object","required":["id","class_id","type","created_at","updated_at"],"title":"FrozenShape","description":"One frozen annotation as serialized into an S3 version manifest by the\nfreeze worker (`job_worker._serialize_ann`). Distinct from `AnnotationResponse`:\nthe manifest omits `asset_id` (implied by the manifest file) and carries no\n`locked`. `geometry` is the constraint-free READ union (ADR-0104): it declares\nthe shape for the SDK without re-imposing write-time value constraints on a\nfrozen snapshot (`data` stays loose passthrough). Structurally safe: geometry\nkeys only ever entered manifests via the strict write union post-0072.\n\nLenient (`extra=\"ignore\"`) so a future manifest field never breaks a versioned\nread or export of an older S3 blob. This is the shared shape typed by\n`ManifestShape` in `app/schemas/manifest.py` and re-exposed on the wire via\n`VersionAssetDetail.shapes`."},"GuidelineImageUploadInitRequest":{"properties":{"filename":{"type":"string","maxLength":255,"minLength":1,"title":"Filename","description":"Original image filename to upload."},"size_bytes":{"type":"integer","maximum":10485760.0,"exclusiveMinimum":0.0,"title":"Size Bytes","description":"Per-image hard cap is 10 MB; over → 422 here, 413 on the route."},"content_type":{"type":"string","maxLength":64,"minLength":1,"title":"Content Type","description":"MIME type of the image being uploaded."}},"type":"object","required":["filename","size_bytes","content_type"],"title":"GuidelineImageUploadInitRequest"},"GuidelineImageUploadInitResponse":{"properties":{"upload_url":{"type":"string","title":"Upload Url","description":"Presigned PUT URL to upload the image bytes to."},"public_url":{"type":"string","title":"Public Url","description":"Presigned GET URL (1-hour TTL) to embed in the guideline Markdown."},"expires_at":{"type":"string","format":"date-time","title":"Expires At","description":"When the presigned URLs expire."}},"type":"object","required":["upload_url","public_url","expires_at"],"title":"GuidelineImageUploadInitResponse","description":"Returned URLs are both presigned + scoped to one upload. The\n`public_url` is a presigned GET valid for 1 hour; it's the value the\nFE drops into the markdown source. When the labeler reads the\nguideline later via `GET /guideline`, the BE re-signs every image URL\nin the doc to a fresh 1-hour window so labelers can always render."},"HTTPValidationError":{"properties":{"detail":{"items":{"$ref":"#/components/schemas/ValidationError"},"type":"array","title":"Detail"}},"type":"object","title":"HTTPValidationError"},"ImageClassificationCreate":{"properties":{"kind":{"type":"string","const":"classification","title":"Kind","default":"classification"},"name":{"type":"string","maxLength":255,"minLength":1,"title":"Name"},"color":{"type":"string","pattern":"^#[0-9A-Fa-f]{6}$","title":"Color","default":"#888888"},"classification_config":{"$ref":"#/components/schemas/ClassificationConfig"}},"type":"object","required":["name","classification_config"],"title":"ImageClassificationCreate","description":"Create an image-level classification (`kind='classification'`, no owner)."},"ImageClassificationResponse":{"properties":{"id":{"type":"string","format":"uuid","title":"Id"},"kind":{"$ref":"#/components/schemas/ClassKind"},"name":{"type":"string","title":"Name"},"color":{"type":"string","title":"Color"},"owner_class_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Owner Class Id"},"archived":{"type":"boolean","title":"Archived"},"classification_config":{"anyOf":[{"$ref":"#/components/schemas/ClassificationConfig"},{"type":"null"}]},"created_at":{"type":"string","format":"date-time","title":"Created At"},"updated_at":{"type":"string","format":"date-time","title":"Updated At"}},"type":"object","required":["id","kind","name","color","archived","created_at","updated_at"],"title":"ImageClassificationResponse"},"ImageRef":{"properties":{"image_b64":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Image B64","description":"Base64-encoded image bytes."}},"type":"object","title":"ImageRef"},"ImageSize":{"properties":{"width":{"type":"integer","title":"Width","description":"Image width in pixels.","default":0},"height":{"type":"integer","title":"Height","description":"Image height in pixels.","default":0}},"type":"object","title":"ImageSize","description":"Decoded image dimensions echoed on a predict response, so the FE can scale\npixel-space geometry without re-reading the image. Shared by the deployment\nand foundation (ZERO) predict responses."},"InvitationCreate":{"properties":{"email":{"type":"string","maxLength":254,"format":"email","title":"Email","description":"Email address to invite."},"role":{"$ref":"#/components/schemas/WorkspaceRole","description":"Workspace role granted when the invitation is accepted.","default":"member"}},"type":"object","required":["email"],"title":"InvitationCreate","description":"An invitation is email + role, nothing else (ADR-0112 principle carried\nforward): the invitee names themselves at sign-up."},"InvitationPage":{"properties":{"items":{"items":{"$ref":"#/components/schemas/InvitationResponse"},"type":"array","title":"Items","description":"This workspace's invitations, newest first. Unpaginated — bounded by the per-tenant user quota."}},"type":"object","required":["items"],"title":"InvitationPage"},"InvitationResponse":{"properties":{"id":{"type":"string","format":"uuid","title":"Id","description":"Invitation id."},"email":{"type":"string","maxLength":254,"format":"email","title":"Email","description":"Invited email — acceptance requires an account holding exactly this address."},"role":{"$ref":"#/components/schemas/WorkspaceRole","description":"Role granted on acceptance."},"status":{"$ref":"#/components/schemas/InvitationStatus","description":"pending | accepted | declined | revoked | expired."},"accept_url":{"type":"string","title":"Accept Url","description":"Hosted accept link (from the invitation email). Copyable — hand it over Slack/chat as an alternative to the email. Possession alone grants nothing: acceptance still requires signing in with the invited email."},"expires_at":{"type":"string","format":"date-time","title":"Expires At","description":"After this the invitation can no longer be accepted."},"created_at":{"type":"string","format":"date-time","title":"Created At","description":"When the invitation was sent."}},"type":"object","required":["id","email","role","status","accept_url","expires_at","created_at"],"title":"InvitationResponse"},"InvitationStatus":{"type":"string","enum":["pending","accepted","declined","revoked","expired"],"title":"InvitationStatus","description":"Lifecycle of a workspace invitation (ADR-0113). Terminal states are\nkept as history; only `pending` rows are actionable/unique per email."},"JobKind":{"type":"string","enum":["build_project_version_manifest","bulk_delete_image_assets","bulk_update_tags_project","bulk_add_assets_project","bulk_remove_assets_project","bulk_assign_assets_project","bulk_unassign_assets_project","bulk_approve_assets_project","bulk_reject_assets_project","bulk_create_annotations","bulk_delete_annotations","embed_dataset","export_dataset","auto_label_project","cleanup_s3_objects","delete_project_cascade","delete_dataset_cascade","duplicate_project_scope","bulk_set_status_assets_project"],"title":"JobKind"},"JobPage":{"properties":{"items":{"items":{"$ref":"#/components/schemas/JobResponse"},"type":"array","title":"Items"},"next_cursor":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Next Cursor"},"total":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Total"}},"type":"object","required":["items"],"title":"JobPage"},"JobResponse":{"properties":{"id":{"type":"string","format":"uuid","title":"Id","description":"Server-assigned job identifier."},"kind":{"$ref":"#/components/schemas/JobKind","description":"The kind of work this job performs."},"target":{"additionalProperties":true,"type":"object","title":"Target","description":"Kind-specific parameters describing what the job operates on (shape varies per `kind`; internal orchestration fields are omitted)."},"status":{"$ref":"#/components/schemas/JobStatus","description":"Current lifecycle state of the job."},"total":{"type":"integer","title":"Total","description":"Total number of work items the job will process."},"processed":{"type":"integer","title":"Processed","description":"Number of work items processed so far."},"succeeded":{"type":"integer","title":"Succeeded","description":"Number of processed items that succeeded."},"failed":{"type":"integer","title":"Failed","description":"Number of processed items that failed."},"skipped":{"type":"integer","title":"Skipped","description":"Number of items skipped (ineligible / already in target state).","default":0},"error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error","description":"Failure message when the job errored, else null."},"result_summary":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Result Summary","description":"Kind-specific summary of the outcome, populated as the job runs and finishes. Stays a plain object here because the key set varies per `kind` (the union isn't a useful client type); the per-kind keys are registry-validated on the write side (`app/job_results.py`), so a given `kind` always carries the same shape."},"created_by":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Created By","description":"User who created the job, or null if system-initiated."},"created_at":{"type":"string","format":"date-time","title":"Created At","description":"When the job was created."},"completed_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Completed At","description":"When the job reached a terminal state, or null if still in flight."},"pct":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Pct","description":"Derived progress percent. A COMPLETED job is always 100 — even a no-op\nwith total==0 finished all of its (zero) work; showing 0/null on a done job\nwas misleading. Otherwise processed/total, or None when total is unknown\n(freshly-queued, denominator not yet resolved).","readOnly":true}},"type":"object","required":["id","kind","target","status","total","processed","succeeded","failed","error","created_by","created_at","completed_at","pct"],"title":"JobResponse"},"JobStatus":{"type":"string","enum":["pending","queued","running","completed","failed","canceled"],"title":"JobStatus"},"KeypointGeometry":{"properties":{"type":{"type":"string","const":"keypoint","title":"Type","default":"keypoint"},"points":{"items":{"$ref":"#/components/schemas/KeypointNodeGeom"},"type":"array","minItems":1,"title":"Points"}},"type":"object","required":["points"],"title":"KeypointGeometry"},"KeypointGeometryRead":{"properties":{"type":{"type":"string","const":"keypoint","title":"Type","default":"keypoint"},"points":{"items":{"$ref":"#/components/schemas/KeypointNodeGeomRead"},"type":"array","title":"Points"}},"additionalProperties":true,"type":"object","required":["points"],"title":"KeypointGeometryRead"},"KeypointNode":{"properties":{"id":{"type":"integer","minimum":0.0,"title":"Id"},"name":{"type":"string","maxLength":64,"minLength":1,"title":"Name"},"default_position":{"anyOf":[{"$ref":"#/components/schemas/KeypointPosition"},{"type":"null"}]}},"type":"object","required":["id","name"],"title":"KeypointNode"},"KeypointNodeGeom":{"properties":{"x":{"type":"number","title":"X"},"y":{"type":"number","title":"Y"},"visibility":{"type":"integer","maximum":2.0,"minimum":0.0,"title":"Visibility","default":2}},"type":"object","required":["x","y"],"title":"KeypointNodeGeom"},"KeypointNodeGeomRead":{"properties":{"x":{"type":"number","title":"X"},"y":{"type":"number","title":"Y"},"visibility":{"type":"integer","title":"Visibility","default":2}},"additionalProperties":true,"type":"object","required":["x","y"],"title":"KeypointNodeGeomRead"},"KeypointPosition":{"properties":{"x":{"type":"number","maximum":1.0,"minimum":0.0,"title":"X"},"y":{"type":"number","maximum":1.0,"minimum":0.0,"title":"Y"}},"type":"object","required":["x","y"],"title":"KeypointPosition","description":"Normalized (0-1) (x, y) inside a unit bbox. Origin is top-left; y grows\ndown. The FE multiplies by the drawn bbox dimensions to seed each joint\nwhen the user creates a new keypoint annotation by dragging a rect."},"KeypointTopology":{"properties":{"version":{"type":"integer","minimum":1.0,"title":"Version","default":1},"dim":{"type":"integer","enum":[2,3],"title":"Dim","default":2},"nodes":{"items":{"$ref":"#/components/schemas/KeypointNode"},"type":"array","maxItems":512,"minItems":1,"title":"Nodes"},"edges":{"items":{"items":{"type":"integer"},"type":"array"},"type":"array","title":"Edges"},"visibility_model":{"$ref":"#/components/schemas/VisibilityModel","default":"coco_ternary"}},"type":"object","required":["nodes"],"title":"KeypointTopology","description":"Per-label keypoint topology — names the nodes, defines skeleton edges.\nADR-0030. Stored on `ProjectClass.keypoint_topology` as JSONB.\n\n`nodes` are 0-indexed by their position in the array; the `id` field is\nthe explicit canonical index and MUST equal the array index. `edges` are\n0-indexed pairs into `nodes`. Empty `edges` = single-keypoint label\n(no skeleton render)."},"LabelingContext":{"properties":{"asset_state":{"$ref":"#/components/schemas/AssetStateResponse","description":"The asset's per-project workflow state."},"asset":{"$ref":"#/components/schemas/AssetSummary","description":"Asset metadata for the labeler page."},"download_url":{"type":"string","title":"Download Url","description":"Presigned GET URL for the asset's full-resolution bytes."},"download_url_expires_at":{"type":"string","format":"date-time","title":"Download Url Expires At","description":"When `download_url` expires."},"shapes":{"items":{"$ref":"#/components/schemas/AnnotationResponse"},"type":"array","title":"Shapes","description":"All annotations on the asset in this project."},"annotations":{"items":{"$ref":"#/components/schemas/AnnotationResponse"},"type":"array","title":"Annotations","description":"Legacy alias of `shapes`; prefer `shapes`."},"classes":{"items":{"$ref":"#/components/schemas/ClassResponse"},"type":"array","title":"Classes","description":"Object classes with inline attributes (ADR-0090)."},"image_classifications":{"items":{"$ref":"#/components/schemas/ImageClassificationResponse"},"type":"array","title":"Image Classifications","description":"Image-level classifications for this project."}},"type":"object","required":["asset_state","asset","download_url","download_url_expires_at"],"title":"LabelingContext","description":"Bundled labeler-page payload: state + asset metadata + annotations + classes\n+ presigned download URL. One round-trip instead of four.\n\n`annotations` (legacy) is kept for backward-compat — it equals `shapes`.\nNew FE code should read `shapes` directly."},"LabelingTimeHeartbeat":{"properties":{"asset_id":{"type":"string","format":"uuid","title":"Asset Id","description":"Project asset the labeler is actively working on."},"class_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Class Id","description":"Project class currently being labeled, if any. Omit when the active time isn't attributable to a specific class."},"active_seconds":{"type":"integer","maximum":30.0,"exclusiveMinimum":0.0,"title":"Active Seconds","description":"Active seconds since the previous heartbeat. Capped at 30s (= 2× the 15s heartbeat cadence) to prevent crafted-payload inflation."},"heartbeat_id":{"type":"string","format":"uuid","title":"Heartbeat Id","description":"Client-generated UUID v4 per heartbeat. Server deduplicates within a 5-minute sliding window per user; replays are silently no-op'd (204)."}},"type":"object","required":["asset_id","active_seconds","heartbeat_id"],"title":"LabelingTimeHeartbeat","description":"Body of POST /labeling-time/heartbeat. Fired by the FE every ~15s\nwhile a labeler is actively working on a project asset."},"LabelingTimeReport":{"properties":{"items":{"items":{"$ref":"#/components/schemas/LabelingTimeRow"},"type":"array","title":"Items","description":"Aggregated labeling-time rows, one per group_by dimension combination."},"next_cursor":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Next Cursor"}},"type":"object","required":["items"],"title":"LabelingTimeReport","description":"GET /labeling-time response."},"LabelingTimeRow":{"properties":{"user_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"User Id","description":"Labeler this row aggregates, when group_by includes 'user'."},"class_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Class Id","description":"Project class this row aggregates, when group_by includes 'class'."},"asset_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Asset Id","description":"Project asset this row aggregates, when group_by includes 'asset'."},"day":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"Day","description":"Calendar day this row aggregates, when group_by includes 'day'."},"total_seconds":{"type":"integer","title":"Total Seconds","description":"Total active labeling seconds for this row's dimension combination."}},"type":"object","required":["total_seconds"],"title":"LabelingTimeRow","description":"One report row. Columns are populated when their dimension is in\nthe group_by list — otherwise they're absent (treated as aggregated)."},"MappedAnnotationCreate":{"properties":{"asset_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Asset Id"},"filename":{"anyOf":[{"type":"string","maxLength":255,"minLength":1},{"type":"null"}],"title":"Filename"},"id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Id"},"class_id":{"type":"string","format":"uuid","title":"Class Id"},"type":{"$ref":"#/components/schemas/AnnotationType"},"geometry":{"anyOf":[{"oneOf":[{"$ref":"#/components/schemas/BboxGeometry"},{"$ref":"#/components/schemas/PolygonGeometry"},{"$ref":"#/components/schemas/KeypointGeometry"}],"discriminator":{"propertyName":"type","mapping":{"bbox":"#/components/schemas/BboxGeometry","keypoint":"#/components/schemas/KeypointGeometry","polygon":"#/components/schemas/PolygonGeometry"}}},{"type":"null"}],"title":"Geometry"},"data":{"additionalProperties":true,"type":"object","title":"Data"},"instance_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Instance Id"},"locked":{"type":"boolean","title":"Locked","default":false}},"type":"object","required":["class_id","type"],"title":"MappedAnnotationCreate","description":"One row of a project-scoped flat create. References its target asset by\n`asset_id` (UUID) or `filename` (resolved within the project's dataset via\nthe unique (dataset, filename) index); `asset_id` wins if both are given.\nGeometry rules match `AnnotationCreate`. No `parent_id` — cross-row parent\nlinks aren't expressible in a flat mapped list."},"MemberAdd":{"properties":{"user_id":{"type":"string","format":"uuid","title":"User Id"},"role":{"$ref":"#/components/schemas/ProjectRole"}},"type":"object","required":["user_id","role"],"title":"MemberAdd"},"MemberPage":{"properties":{"items":{"items":{"$ref":"#/components/schemas/MemberResponse"},"type":"array","title":"Items"},"next_cursor":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Next Cursor"}},"type":"object","required":["items"],"title":"MemberPage","description":"ADR-0012 pagination envelope for the member listing. The member set is\nbounded (ADR-0078 caps `MAX_MEMBERS_PER_PROJECT` at <=50) and returned whole,\nso `next_cursor` is always None — the shape exists for contract uniformity."},"MemberResponse":{"properties":{"user_id":{"type":"string","format":"uuid","title":"User Id","description":"Id of the member user."},"role":{"$ref":"#/components/schemas/ProjectRole","description":"Member's project role (manager, reviewer, or labeler)."},"user":{"anyOf":[{"$ref":"#/components/schemas/UserSummary"},{"type":"null"}],"description":"Member profile; populated by routes that JOIN, else None."}},"type":"object","required":["user_id","role"],"title":"MemberResponse"},"MemberUpdate":{"properties":{"role":{"$ref":"#/components/schemas/ProjectRole"}},"type":"object","required":["role"],"title":"MemberUpdate"},"ModelRef":{"properties":{"kind":{"$ref":"#/components/schemas/ModelRefKind"},"deployment_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Deployment Id"},"foundation_key":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Foundation Key"}},"type":"object","required":["kind"],"title":"ModelRef","description":"Which model auto-label uses. `deployment` = a trained-model SageMaker\nendpoint (batch + assist); `foundation` = an open-vocab model like ZERO\n(assist only — see ADR-0075)."},"ModelRefKind":{"type":"string","enum":["deployment","foundation"],"title":"ModelRefKind","description":"Which model backs an auto-label run (stored in project.auto_label_config JSONB)."},"MultiSourceAnnotations":{"properties":{"asset_id":{"type":"string","format":"uuid","title":"Asset Id"},"sources":{"items":{"$ref":"#/components/schemas/AnnotationsBySource"},"type":"array","title":"Sources"}},"type":"object","required":["asset_id","sources"],"title":"MultiSourceAnnotations"},"MultipartCompletePart":{"properties":{"part_number":{"type":"integer","minimum":1.0,"title":"Part Number"},"etag":{"type":"string","minLength":1,"title":"Etag"}},"type":"object","required":["part_number","etag"],"title":"MultipartCompletePart"},"MultipartPartUrl":{"properties":{"part_number":{"type":"integer","title":"Part Number"},"url":{"type":"string","title":"Url"}},"type":"object","required":["part_number","url"],"title":"MultipartPartUrl"},"MyInvitation":{"properties":{"id":{"type":"string","format":"uuid","title":"Id","description":"Invitation id — accept/decline target."},"tenant_slug":{"type":"string","title":"Tenant Slug","description":"Workspace the invitation joins."},"tenant_name":{"type":"string","title":"Tenant Name","description":"Workspace display name."},"role":{"$ref":"#/components/schemas/WorkspaceRole","description":"Role granted on acceptance."},"invited_by_email":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Invited By Email","description":"Email of the admin who sent it, when still resolvable."},"expires_at":{"type":"string","format":"date-time","title":"Expires At","description":"After this, the invitation can't be accepted."},"created_at":{"type":"string","format":"date-time","title":"Created At","description":"When the invitation was sent."}},"type":"object","required":["id","tenant_slug","tenant_name","role","invited_by_email","expires_at","created_at"],"title":"MyInvitation","description":"A pending invitation addressed to this account's email\n(`GET /me/invitations` item). Accept/decline via its id."},"MyInvitationsResponse":{"properties":{"items":{"items":{"$ref":"#/components/schemas/MyInvitation"},"type":"array","title":"Items","description":"Pending, unexpired invitations whose email exactly matches the account's email."}},"type":"object","required":["items"],"title":"MyInvitationsResponse"},"MyTenantsResponse":{"properties":{"items":{"items":{"$ref":"#/components/schemas/TenantMembership"},"type":"array","title":"Items","description":"Every workspace this account belongs to. Unpaginated — bounded by how many workspaces one person can realistically join."}},"type":"object","required":["items"],"title":"MyTenantsResponse"},"OutputGeometry":{"type":"string","enum":["bbox","polygon"],"title":"OutputGeometry","description":"Geometry kinds a model can emit (subset of AnnotationType). Used by the\nserving/deployment + foundation-model capability descriptors."},"OverallMetrics":{"properties":{"precision":{"type":"number","title":"Precision","default":0.0},"recall":{"type":"number","title":"Recall","default":0.0},"f1":{"type":"number","title":"F1","default":0.0},"map":{"type":"number","title":"Map","default":0.0},"map_50":{"type":"number","title":"Map 50","default":0.0}},"additionalProperties":false,"type":"object","title":"OverallMetrics","description":"Overall detection/segmentation metrics at the F1-optimal confidence.\n`map`/`map_50` are injected post-hoc by `trainer._score_split` (torchmetrics),\nso `evaluate()` returns them unset — hence Optional-with-default."},"OwnerCreate":{"properties":{"email":{"type":"string","maxLength":254,"format":"email","title":"Email","description":"Email address of the tenant owner; used as their login identity."},"first_name":{"type":"string","maxLength":100,"minLength":1,"title":"First Name","description":"Given name of the tenant owner."},"last_name":{"type":"string","maxLength":100,"minLength":1,"title":"Last Name","description":"Family name of the tenant owner."},"password":{"type":"string","maxLength":128,"minLength":10,"title":"Password","description":"Initial password for the tenant owner account."}},"type":"object","required":["email","first_name","last_name","password"],"title":"OwnerCreate"},"OwnerResponse":{"properties":{"id":{"type":"string","format":"uuid","title":"Id","description":"Unique identifier of the owner user."},"email":{"type":"string","title":"Email","description":"Email address of the owner user."},"first_name":{"type":"string","title":"First Name","description":"Given name of the owner user."},"last_name":{"type":"string","title":"Last Name","description":"Family name of the owner user."}},"type":"object","required":["id","email","first_name","last_name"],"title":"OwnerResponse"},"ParamSpec":{"properties":{"min":{"type":"number","title":"Min","description":"Minimum allowed value for the parameter."},"max":{"type":"number","title":"Max","description":"Maximum allowed value for the parameter."},"default":{"type":"number","title":"Default","description":"Applied when the parameter is omitted from predict. Seeded from the model's train-time VALIDATION split: detection confidence and IoU are swept jointly under serving-identical per-class NMS; segmentation exposes confidence only because its serving postprocessor does not apply mask NMS. max_detections is data-derived (max ground-truth instances in any single val image, floor 10, cap 1000). Deliberately NOT the scorecard's `optimal_confidence`, which is a test-split diagnostic measured without NMS — the two answer different questions and will differ slightly."}},"type":"object","required":["min","max","default"],"title":"ParamSpec"},"PerClassMetrics":{"properties":{"class":{"type":"string","title":"Class"},"precision":{"type":"number","title":"Precision"},"recall":{"type":"number","title":"Recall"},"f1":{"type":"number","title":"F1"},"support":{"type":"integer","title":"Support"}},"additionalProperties":false,"type":"object","required":["class","precision","recall","f1","support"],"title":"PerClassMetrics","description":"One class's P/R/F1 + GT support at the chosen threshold."},"PointAnchor":{"properties":{"x":{"type":"number","maximum":2.0,"minimum":-1.0,"title":"X"},"y":{"type":"number","maximum":2.0,"minimum":-1.0,"title":"Y"}},"type":"object","required":["x","y"],"title":"PointAnchor"},"PolygonGeometry":{"properties":{"type":{"type":"string","const":"polygon","title":"Type","default":"polygon"},"polygons":{"items":{"$ref":"#/components/schemas/PolygonPart"},"type":"array","minItems":1,"title":"Polygons"}},"type":"object","required":["polygons"],"title":"PolygonGeometry"},"PolygonGeometryRead":{"properties":{"type":{"type":"string","const":"polygon","title":"Type","default":"polygon"},"polygons":{"items":{"$ref":"#/components/schemas/PolygonPartRead"},"type":"array","title":"Polygons"}},"additionalProperties":true,"type":"object","required":["polygons"],"title":"PolygonGeometryRead"},"PolygonPart":{"properties":{"exterior":{"items":{"prefixItems":[{"type":"number"},{"type":"number"}],"type":"array","maxItems":2,"minItems":2},"type":"array","minItems":3,"title":"Exterior"},"holes":{"items":{"items":{"prefixItems":[{"type":"number"},{"type":"number"}],"type":"array","maxItems":2,"minItems":2},"type":"array"},"type":"array","title":"Holes"}},"type":"object","required":["exterior"],"title":"PolygonPart","description":"One part of a (possibly multi-part) polygon: an exterior ring + optional hole rings."},"PolygonPartRead":{"properties":{"exterior":{"items":{"prefixItems":[{"type":"number"},{"type":"number"}],"type":"array","maxItems":2,"minItems":2},"type":"array","title":"Exterior"},"holes":{"items":{"items":{"prefixItems":[{"type":"number"},{"type":"number"}],"type":"array","maxItems":2,"minItems":2},"type":"array"},"type":"array","title":"Holes"}},"additionalProperties":true,"type":"object","required":["exterior"],"title":"PolygonPartRead"},"PredictRequest":{"properties":{"image_b64":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Image B64","description":"Base64-encoded image bytes."},"confidence":{"anyOf":[{"type":"number","maximum":1.0,"minimum":0.0},{"type":"null"}],"title":"Confidence","description":"Confidence threshold; predictions below it are dropped."},"iou":{"anyOf":[{"type":"number","maximum":1.0,"minimum":0.0},{"type":"null"}],"title":"Iou","description":"IoU threshold for non-max suppression."},"max_detections":{"anyOf":[{"type":"integer","maximum":1000.0,"minimum":1.0},{"type":"null"}],"title":"Max Detections","description":"Maximum number of predictions to return."},"classes":{"anyOf":[{"items":{"type":"integer"},"type":"array"},{"type":"null"}],"title":"Classes","description":"Restrict predictions to these class_ids."}},"type":"object","title":"PredictRequest"},"PredictResponse":{"properties":{"predictions":{"items":{"$ref":"#/components/schemas/Prediction"},"type":"array","title":"Predictions","description":"Predictions for the image."},"image":{"$ref":"#/components/schemas/ImageSize","description":"Decoded image dimensions."},"inference_ms":{"type":"integer","title":"Inference Ms","description":"Server-side inference time in milliseconds."}},"type":"object","required":["predictions","image","inference_ms"],"title":"PredictResponse"},"Prediction":{"properties":{"type":{"$ref":"#/components/schemas/OutputGeometry","description":"Geometry kind of this prediction."},"class_id":{"type":"integer","title":"Class Id","description":"0-indexed model class of the prediction."},"class_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Class Name","description":"Class name, if resolvable."},"geometry":{"oneOf":[{"$ref":"#/components/schemas/BboxGeometry"},{"$ref":"#/components/schemas/PolygonGeometry"},{"$ref":"#/components/schemas/KeypointGeometry"}],"title":"Geometry","description":"Typed geometry (bbox/polygon/keypoint) — the same discriminated shape as annotation.geometry, so a prediction maps onto an annotation without translation.","discriminator":{"propertyName":"type","mapping":{"bbox":"#/components/schemas/BboxGeometry","keypoint":"#/components/schemas/KeypointGeometry","polygon":"#/components/schemas/PolygonGeometry"}}},"confidence":{"type":"number","title":"Confidence","description":"Model confidence for this prediction."}},"type":"object","required":["type","class_id","geometry","confidence"],"title":"Prediction"},"ProjectAnnotationBatchCreate":{"properties":{"annotations":{"items":{"$ref":"#/components/schemas/MappedAnnotationCreate"},"type":"array","maxItems":1000,"minItems":1,"title":"Annotations"},"source":{"$ref":"#/components/schemas/AnnotationSource","default":"manual"},"replace":{"type":"boolean","title":"Replace","default":false}},"type":"object","required":["annotations"],"title":"ProjectAnnotationBatchCreate","description":"Body for `POST /{pid}/annotations/batch-create` — sync, flat mapped list,\nall-or-nothing (an invalid row → 422 with its index). Cap\n`MAX_SYNC_BATCH_SIZE`. `source` defaults to `manual`."},"ProjectAnnotationBatchCreateResponse":{"properties":{"total":{"type":"integer","title":"Total"},"created":{"type":"integer","title":"Created"},"replaced":{"type":"integer","title":"Replaced"}},"type":"object","required":["total","created","replaced"],"title":"ProjectAnnotationBatchCreateResponse"},"ProjectAnnotationBulkCreate":{"properties":{"annotations":{"anyOf":[{"items":{"$ref":"#/components/schemas/MappedAnnotationCreate"},"type":"array","minItems":1},{"type":"null"}],"title":"Annotations"},"import_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Import Id"},"source":{"$ref":"#/components/schemas/AnnotationSource","default":"imported"},"replace":{"type":"boolean","title":"Replace","default":false}},"type":"object","title":"ProjectAnnotationBulkCreate","description":"Body for `POST /{pid}/annotations/bulk-create` — async. Provide EITHER\n`annotations` inline (bounded by the ~6 MB Lambda request payload) OR an\n`import_id` from `import-init` (S3-staged NDJSON, for large imports).\nExactly one. `source` defaults to `imported` (the import path)."},"ProjectAssetsBatchAddRequest":{"properties":{"asset_ids":{"items":{"type":"string","format":"uuid"},"type":"array","minItems":1,"title":"Asset Ids"}},"type":"object","required":["asset_ids"],"title":"ProjectAssetsBatchAddRequest","description":"Sync `batch-add` body — explicit asset_ids only, ≤MAX_SYNC_BATCH_SIZE."},"ProjectAssetsBatchAddResponse":{"properties":{"total":{"type":"integer","title":"Total"},"added":{"type":"integer","title":"Added"},"skipped":{"type":"integer","title":"Skipped"},"results":{"items":{"$ref":"#/components/schemas/ProjectAssetsBatchAddResultItem"},"type":"array","title":"Results"}},"type":"object","required":["total","added","skipped","results"],"title":"ProjectAssetsBatchAddResponse","description":"Canonical sync batch-op envelope (ADR-0041): `total == added + skipped`,\n`len(results) == total`. `skipped` = already-in-scope (idempotent re-add)."},"ProjectAssetsBatchAddResultItem":{"properties":{"id":{"type":"string","format":"uuid","title":"Id"},"status":{"type":"string","enum":["added","skipped"],"title":"Status"}},"type":"object","required":["id","status"],"title":"ProjectAssetsBatchAddResultItem"},"ProjectAssetsBatchApproveRequest":{"properties":{"asset_ids":{"items":{"type":"string","format":"uuid"},"type":"array","minItems":1,"title":"Asset Ids"}},"type":"object","required":["asset_ids"],"title":"ProjectAssetsBatchApproveRequest","description":"Sync batch-approve. Explicit asset_ids only."},"ProjectAssetsBatchApproveResponse":{"properties":{"total":{"type":"integer","title":"Total"},"approved":{"type":"integer","title":"Approved"},"skipped":{"type":"integer","title":"Skipped"},"results":{"items":{"$ref":"#/components/schemas/ProjectAssetsBatchApproveResultItem"},"type":"array","title":"Results"}},"type":"object","required":["total","approved","skipped","results"],"title":"ProjectAssetsBatchApproveResponse"},"ProjectAssetsBatchApproveResultItem":{"properties":{"id":{"type":"string","format":"uuid","title":"Id"},"status":{"type":"string","enum":["approved","skipped"],"title":"Status"}},"type":"object","required":["id","status"],"title":"ProjectAssetsBatchApproveResultItem"},"ProjectAssetsBatchAssignRequest":{"properties":{"asset_ids":{"items":{"type":"string","format":"uuid"},"type":"array","minItems":1,"title":"Asset Ids"},"user_id":{"type":"string","format":"uuid","title":"User Id"}},"type":"object","required":["asset_ids","user_id"],"title":"ProjectAssetsBatchAssignRequest","description":"Sync batch-assign. Explicit asset_ids only (≤MAX_SYNC_BATCH_SIZE)."},"ProjectAssetsBatchAssignResponse":{"properties":{"total":{"type":"integer","title":"Total"},"assigned":{"type":"integer","title":"Assigned"},"skipped":{"type":"integer","title":"Skipped"},"results":{"items":{"$ref":"#/components/schemas/ProjectAssetsBatchAssignResultItem"},"type":"array","title":"Results"}},"type":"object","required":["total","assigned","skipped","results"],"title":"ProjectAssetsBatchAssignResponse"},"ProjectAssetsBatchAssignResultItem":{"properties":{"id":{"type":"string","format":"uuid","title":"Id"},"status":{"type":"string","enum":["assigned","skipped"],"title":"Status"}},"type":"object","required":["id","status"],"title":"ProjectAssetsBatchAssignResultItem"},"ProjectAssetsBatchRejectRequest":{"properties":{"asset_ids":{"items":{"type":"string","format":"uuid"},"type":"array","minItems":1,"title":"Asset Ids"},"reason":{"anyOf":[{"type":"string","maxLength":1000},{"type":"null"}],"title":"Reason"}},"type":"object","required":["asset_ids"],"title":"ProjectAssetsBatchRejectRequest","description":"Sync batch-reject. Explicit asset_ids only."},"ProjectAssetsBatchRejectResponse":{"properties":{"total":{"type":"integer","title":"Total"},"rejected":{"type":"integer","title":"Rejected"},"skipped":{"type":"integer","title":"Skipped"},"results":{"items":{"$ref":"#/components/schemas/ProjectAssetsBatchRejectResultItem"},"type":"array","title":"Results"}},"type":"object","required":["total","rejected","skipped","results"],"title":"ProjectAssetsBatchRejectResponse"},"ProjectAssetsBatchRejectResultItem":{"properties":{"id":{"type":"string","format":"uuid","title":"Id"},"status":{"type":"string","enum":["rejected","skipped"],"title":"Status"}},"type":"object","required":["id","status"],"title":"ProjectAssetsBatchRejectResultItem"},"ProjectAssetsBatchRemoveRequest":{"properties":{"asset_ids":{"items":{"type":"string","format":"uuid"},"type":"array","minItems":1,"title":"Asset Ids"}},"type":"object","required":["asset_ids"],"title":"ProjectAssetsBatchRemoveRequest","description":"Sync `batch-remove` body — explicit asset_ids only, ≤MAX_SYNC_BATCH_SIZE."},"ProjectAssetsBatchRemoveResponse":{"properties":{"total":{"type":"integer","title":"Total"},"removed":{"type":"integer","title":"Removed"},"skipped":{"type":"integer","title":"Skipped"},"results":{"items":{"$ref":"#/components/schemas/ProjectAssetsBatchRemoveResultItem"},"type":"array","title":"Results"}},"type":"object","required":["total","removed","skipped","results"],"title":"ProjectAssetsBatchRemoveResponse","description":"Canonical sync batch-op envelope (ADR-0041): `total == removed + skipped`,\n`len(results) == total`. `skipped` = not-in-project, nothing to drop."},"ProjectAssetsBatchRemoveResultItem":{"properties":{"id":{"type":"string","format":"uuid","title":"Id"},"status":{"type":"string","enum":["removed","skipped"],"title":"Status"}},"type":"object","required":["id","status"],"title":"ProjectAssetsBatchRemoveResultItem"},"ProjectAssetsBatchSetStatusRequest":{"properties":{"asset_ids":{"items":{"type":"string","format":"uuid"},"type":"array","minItems":1,"title":"Asset Ids"},"target_status":{"$ref":"#/components/schemas/WorkflowStatus"}},"type":"object","required":["asset_ids","target_status"],"title":"ProjectAssetsBatchSetStatusRequest","description":"Sync batch set-status. Explicit asset_ids only."},"ProjectAssetsBatchSetStatusResponse":{"properties":{"total":{"type":"integer","title":"Total"},"updated":{"type":"integer","title":"Updated"},"skipped":{"type":"integer","title":"Skipped"},"results":{"items":{"$ref":"#/components/schemas/ProjectAssetsBatchSetStatusResultItem"},"type":"array","title":"Results"}},"type":"object","required":["total","updated","skipped","results"],"title":"ProjectAssetsBatchSetStatusResponse"},"ProjectAssetsBatchSetStatusResultItem":{"properties":{"id":{"type":"string","format":"uuid","title":"Id"},"status":{"type":"string","enum":["updated","skipped"],"title":"Status"}},"type":"object","required":["id","status"],"title":"ProjectAssetsBatchSetStatusResultItem"},"ProjectAssetsBatchUnassignRequest":{"properties":{"asset_ids":{"items":{"type":"string","format":"uuid"},"type":"array","minItems":1,"title":"Asset Ids"}},"type":"object","required":["asset_ids"],"title":"ProjectAssetsBatchUnassignRequest","description":"Sync batch-unassign. Explicit asset_ids only."},"ProjectAssetsBatchUnassignResponse":{"properties":{"total":{"type":"integer","title":"Total"},"unassigned":{"type":"integer","title":"Unassigned"},"skipped":{"type":"integer","title":"Skipped"},"results":{"items":{"$ref":"#/components/schemas/ProjectAssetsBatchUnassignResultItem"},"type":"array","title":"Results"}},"type":"object","required":["total","unassigned","skipped","results"],"title":"ProjectAssetsBatchUnassignResponse"},"ProjectAssetsBatchUnassignResultItem":{"properties":{"id":{"type":"string","format":"uuid","title":"Id"},"status":{"type":"string","enum":["unassigned","skipped"],"title":"Status"}},"type":"object","required":["id","status"],"title":"ProjectAssetsBatchUnassignResultItem"},"ProjectAssetsBatchUpdateRequest":{"properties":{"asset_ids":{"items":{"type":"string","format":"uuid"},"type":"array","minItems":1,"title":"Asset Ids"},"tags_add":{"anyOf":[{"items":{"type":"string"},"type":"array","minItems":1},{"type":"null"}],"title":"Tags Add"},"tags_remove":{"anyOf":[{"items":{"type":"string"},"type":"array","minItems":1},{"type":"null"}],"title":"Tags Remove"}},"type":"object","required":["asset_ids"],"title":"ProjectAssetsBatchUpdateRequest","description":"Sync `batch-update-tags` body — explicit asset_ids only, ≤MAX_SYNC_BATCH_SIZE.\nSame dual-arm shape as the async path; FE picks sync when N≤1000 for\ninstant feedback, async for larger / filter-driven."},"ProjectAssetsBatchUpdateResponse":{"properties":{"total":{"type":"integer","title":"Total"},"updated":{"type":"integer","title":"Updated"},"skipped":{"type":"integer","title":"Skipped"},"results":{"items":{"$ref":"#/components/schemas/ProjectAssetsBatchUpdateResultItem"},"type":"array","title":"Results"}},"type":"object","required":["total","updated","skipped","results"],"title":"ProjectAssetsBatchUpdateResponse","description":"Canonical sync batch-op envelope (ADR-0041): `total == updated + skipped`,\n`len(results) == total`. `skipped` = asset wasn't in the project, was already\nin the desired tag state, or was otherwise filtered out by the WHERE clause."},"ProjectAssetsBatchUpdateResultItem":{"properties":{"id":{"type":"string","format":"uuid","title":"Id"},"status":{"type":"string","enum":["updated","skipped"],"title":"Status"}},"type":"object","required":["id","status"],"title":"ProjectAssetsBatchUpdateResultItem"},"ProjectBatchDeleteAnnotationsRequest":{"properties":{"class_ids":{"anyOf":[{"items":{"type":"string","format":"uuid"},"type":"array","minItems":1},{"type":"null"}],"title":"Class Ids","description":"Delete only annotations whose class_id is in this list (ANY-of / IN). Annotation FIELD filter — distinct from the ProjectFilter `classes` asset content-filter. Unknown ids match nothing (entity-ref contract)."},"source":{"anyOf":[{"$ref":"#/components/schemas/AnnotationSource"},{"type":"null"}],"description":"Delete only annotations with this source (`manual` | `imported` | `model`) — e.g. `model` to undo an auto-label run, `imported` to undo a bad import, without touching human work."},"asset_ids":{"items":{"type":"string","format":"uuid"},"type":"array","minItems":1,"title":"Asset Ids","description":"Assets whose matching annotations are deleted. Explicit ids only."}},"additionalProperties":false,"type":"object","required":["asset_ids"],"title":"ProjectBatchDeleteAnnotationsRequest","description":"Sync `batch-delete-annotations` body — explicit asset_ids only,\n≤MAX_SYNC_BATCH_SIZE (ADR-0041). Additionally 413s when the selection\nmatches more than MAX_SYNC_BATCH_SIZE annotations (the sync perf budget\nbounds deleted rows, not selected assets) — use bulk-delete-annotations."},"ProjectBatchDeleteAnnotationsResponse":{"properties":{"total":{"type":"integer","title":"Total","description":"Number of asset ids requested."},"cleared":{"type":"integer","title":"Cleared","description":"Assets that had ≥1 matching annotation deleted."},"skipped":{"type":"integer","title":"Skipped","description":"Assets where nothing was deleted."},"deleted_annotations":{"type":"integer","title":"Deleted Annotations","description":"Total annotations deleted across all assets."},"skipped_locked":{"type":"integer","title":"Skipped Locked","description":"Matched annotations left in place because they are locked (ADR-0084)."},"results":{"items":{"$ref":"#/components/schemas/ProjectBatchDeleteAnnotationsResultItem"},"type":"array","title":"Results","description":"Per-asset outcome, one entry per requested id."}},"type":"object","required":["total","cleared","skipped","deleted_annotations","skipped_locked","results"],"title":"ProjectBatchDeleteAnnotationsResponse","description":"Canonical sync batch-op envelope (ADR-0041): `total == cleared + skipped`\nover ASSETS, `len(results) == total`; `deleted_annotations`/`skipped_locked`\ncarry the annotation-level accounting."},"ProjectBatchDeleteAnnotationsResultItem":{"properties":{"id":{"type":"string","format":"uuid","title":"Id","description":"Requested asset id."},"status":{"type":"string","enum":["cleared","skipped"],"title":"Status","description":"`cleared` = ≥1 annotation deleted; `skipped` = nothing matched (no annotations, predicate matched none, or all matches were locked)."},"deleted":{"type":"integer","title":"Deleted","description":"Annotations deleted on this asset."}},"type":"object","required":["id","status","deleted"],"title":"ProjectBatchDeleteAnnotationsResultItem"},"ProjectBulkAddAssetsRequest":{"properties":{"asset_ids":{"anyOf":[{"items":{"type":"string","format":"uuid"},"type":"array"},{"type":"null"}],"title":"Asset Ids"},"filter":{"anyOf":[{"$ref":"#/components/schemas/AssetFilter"},{"type":"null"}]},"exclude_asset_ids":{"anyOf":[{"items":{"type":"string","format":"uuid"},"type":"array"},{"type":"null"}],"title":"Exclude Asset Ids"}},"type":"object","title":"ProjectBulkAddAssetsRequest","description":"Async `bulk-add` body. `asset_ids` lists dataset asset ids to\npull into the project; `filter` (AssetFilter — dataset-scoped) picks\nthem by metadata. Exactly one mode. `exclude_asset_ids` (filter-mode\nonly) deselects rows from the matched set."},"ProjectBulkApproveAssetsRequest":{"properties":{"asset_ids":{"anyOf":[{"items":{"type":"string","format":"uuid"},"type":"array"},{"type":"null"}],"title":"Asset Ids"},"filter":{"anyOf":[{"$ref":"#/components/schemas/ProjectFilter"},{"type":"null"}]},"exclude_asset_ids":{"anyOf":[{"items":{"type":"string","format":"uuid"},"type":"array"},{"type":"null"}],"title":"Exclude Asset Ids"}},"type":"object","title":"ProjectBulkApproveAssetsRequest","description":"Async bulk-approve. No payload. Worker silently skips rows not in\n`in_review`."},"ProjectBulkAssignAssetsRequest":{"properties":{"asset_ids":{"anyOf":[{"items":{"type":"string","format":"uuid"},"type":"array"},{"type":"null"}],"title":"Asset Ids"},"filter":{"anyOf":[{"$ref":"#/components/schemas/ProjectFilter"},{"type":"null"}]},"exclude_asset_ids":{"anyOf":[{"items":{"type":"string","format":"uuid"},"type":"array"},{"type":"null"}],"title":"Exclude Asset Ids"},"user_id":{"type":"string","format":"uuid","title":"User Id"}},"type":"object","required":["user_id"],"title":"ProjectBulkAssignAssetsRequest","description":"Async bulk-assign. `user_id` must be a tenant member (project-member\nor tenant admin); route validates at job-create time so the job never\nenqueues with a bad assignee."},"ProjectBulkDeleteAnnotationsRequest":{"properties":{"class_ids":{"anyOf":[{"items":{"type":"string","format":"uuid"},"type":"array","minItems":1},{"type":"null"}],"title":"Class Ids","description":"Delete only annotations whose class_id is in this list (ANY-of / IN). Annotation FIELD filter — distinct from the ProjectFilter `classes` asset content-filter. Unknown ids match nothing (entity-ref contract)."},"source":{"anyOf":[{"$ref":"#/components/schemas/AnnotationSource"},{"type":"null"}],"description":"Delete only annotations with this source (`manual` | `imported` | `model`) — e.g. `model` to undo an auto-label run, `imported` to undo a bad import, without touching human work."},"asset_ids":{"anyOf":[{"items":{"type":"string","format":"uuid"},"type":"array"},{"type":"null"}],"title":"Asset Ids"},"filter":{"anyOf":[{"$ref":"#/components/schemas/ProjectFilter"},{"type":"null"}]},"exclude_asset_ids":{"anyOf":[{"items":{"type":"string","format":"uuid"},"type":"array"},{"type":"null"}],"title":"Exclude Asset Ids"}},"additionalProperties":false,"type":"object","title":"ProjectBulkDeleteAnnotationsRequest","description":"Async `bulk-delete-annotations` body: ProjectBulkOpRequest selection\n(exactly-one-of `asset_ids` | `filter`, filter-mode `exclude_asset_ids`)\nplus the annotation predicate. Locked shapes are never deleted (reported\nas `skipped_locked` on the job's result_summary)."},"ProjectBulkOpRequest":{"properties":{"asset_ids":{"anyOf":[{"items":{"type":"string","format":"uuid"},"type":"array"},{"type":"null"}],"title":"Asset Ids"},"filter":{"anyOf":[{"$ref":"#/components/schemas/ProjectFilter"},{"type":"null"}]},"exclude_asset_ids":{"anyOf":[{"items":{"type":"string","format":"uuid"},"type":"array"},{"type":"null"}],"title":"Exclude Asset Ids"}},"type":"object","title":"ProjectBulkOpRequest","description":"Shared shape for project-scoped async bulk ops. Exactly one of\n`asset_ids` (explicit list) or `filter` (ProjectFilter — workflow + tags\n+ asset metadata) must be present.\n\n`exclude_asset_ids` (filter-mode only): per-row deselects on top of\n`filter`. Worker applies as `NOT IN` against the matched set so the\n\"select all matching except these few\" UX is safe — critical for\ndestructive ops like bulk-remove which cascade-wipe per-pair\nstate. Ids not in the matched set are silently ignored."},"ProjectBulkRejectAssetsRequest":{"properties":{"asset_ids":{"anyOf":[{"items":{"type":"string","format":"uuid"},"type":"array"},{"type":"null"}],"title":"Asset Ids"},"filter":{"anyOf":[{"$ref":"#/components/schemas/ProjectFilter"},{"type":"null"}]},"exclude_asset_ids":{"anyOf":[{"items":{"type":"string","format":"uuid"},"type":"array"},{"type":"null"}],"title":"Exclude Asset Ids"},"reason":{"anyOf":[{"type":"string","maxLength":1000},{"type":"null"}],"title":"Reason"}},"type":"object","title":"ProjectBulkRejectAssetsRequest","description":"Async bulk-reject. Optional `reason` flows into:\n- `project_asset_state.last_reject_reason`\n- a new Comment row (anchor=asset, origin=workflow_reject) so the\n labeler sees the same reason they'd see for a single-asset reject."},"ProjectBulkRemoveAssetsRequest":{"properties":{"asset_ids":{"anyOf":[{"items":{"type":"string","format":"uuid"},"type":"array"},{"type":"null"}],"title":"Asset Ids"},"filter":{"anyOf":[{"$ref":"#/components/schemas/ProjectFilter"},{"type":"null"}]},"exclude_asset_ids":{"anyOf":[{"items":{"type":"string","format":"uuid"},"type":"array"},{"type":"null"}],"title":"Exclude Asset Ids"}},"type":"object","title":"ProjectBulkRemoveAssetsRequest","description":"Async `bulk-remove` body. Selection is project-scoped — uses\n`ProjectFilter` since you're choosing currently-in-project assets to\ndrop (workflow + tag fields are meaningful). `exclude_asset_ids`\n(filter-mode only) deselects rows from the matched set — critical\nbecause this endpoint cascade-wipes per-(project, asset) state."},"ProjectBulkSetStatusAssetsRequest":{"properties":{"asset_ids":{"anyOf":[{"items":{"type":"string","format":"uuid"},"type":"array"},{"type":"null"}],"title":"Asset Ids"},"filter":{"anyOf":[{"$ref":"#/components/schemas/ProjectFilter"},{"type":"null"}]},"exclude_asset_ids":{"anyOf":[{"items":{"type":"string","format":"uuid"},"type":"array"},{"type":"null"}],"title":"Exclude Asset Ids"},"target_status":{"$ref":"#/components/schemas/WorkflowStatus"}},"type":"object","required":["target_status"],"title":"ProjectBulkSetStatusAssetsRequest","description":"Async bulk set-status. Force-moves the selection to `target_status`\nregardless of current status (manager override — not a guarded transition).\nRows already at the target are silently skipped."},"ProjectBulkUnassignAssetsRequest":{"properties":{"asset_ids":{"anyOf":[{"items":{"type":"string","format":"uuid"},"type":"array"},{"type":"null"}],"title":"Asset Ids"},"filter":{"anyOf":[{"$ref":"#/components/schemas/ProjectFilter"},{"type":"null"}]},"exclude_asset_ids":{"anyOf":[{"items":{"type":"string","format":"uuid"},"type":"array"},{"type":"null"}],"title":"Exclude Asset Ids"}},"type":"object","title":"ProjectBulkUnassignAssetsRequest","description":"Async bulk-unassign. No payload."},"ProjectBulkUpdateTagsRequest":{"properties":{"asset_ids":{"anyOf":[{"items":{"type":"string","format":"uuid"},"type":"array"},{"type":"null"}],"title":"Asset Ids"},"filter":{"anyOf":[{"$ref":"#/components/schemas/ProjectFilter"},{"type":"null"}]},"exclude_asset_ids":{"anyOf":[{"items":{"type":"string","format":"uuid"},"type":"array"},{"type":"null"}],"title":"Exclude Asset Ids"},"tags_add":{"anyOf":[{"items":{"type":"string"},"type":"array","minItems":1},{"type":"null"}],"title":"Tags Add"},"tags_remove":{"anyOf":[{"items":{"type":"string"},"type":"array","minItems":1},{"type":"null"}],"title":"Tags Remove"}},"type":"object","title":"ProjectBulkUpdateTagsRequest","description":"Async `bulk-update-tags` body. Atomic — both arms apply in one\ntransactional batch update. At least one of `tags_add` / `tags_remove`\nmust be non-empty. Tags are validated against the project's `tag_config`\nwhen `free_form=false` (additions only)."},"ProjectCreate":{"properties":{"name":{"type":"string","maxLength":255,"minLength":1,"title":"Name"},"description":{"anyOf":[{"type":"string","maxLength":1000},{"type":"null"}],"title":"Description"},"dataset_id":{"type":"string","format":"uuid","title":"Dataset Id"},"dataset_kind":{"$ref":"#/components/schemas/DatasetKind"},"scope":{"$ref":"#/components/schemas/ProjectScope"},"classes":{"items":{"$ref":"#/components/schemas/ClassCreate"},"type":"array","title":"Classes"},"image_classifications":{"items":{"$ref":"#/components/schemas/ImageClassificationCreate"},"type":"array","title":"Image Classifications"},"assignment_mode":{"$ref":"#/components/schemas/AssignmentMode","default":"self_claim"},"tag_config":{"anyOf":[{"$ref":"#/components/schemas/TagConfig"},{"type":"null"}]}},"type":"object","required":["name","dataset_id","dataset_kind","scope"],"title":"ProjectCreate"},"ProjectDuplicateRequest":{"properties":{"name":{"anyOf":[{"type":"string","maxLength":255},{"type":"null"}],"title":"Name"}},"type":"object","title":"ProjectDuplicateRequest"},"ProjectFilter":{"properties":{"status":{"anyOf":[{"$ref":"#/components/schemas/WorkflowStatus"},{"type":"null"}]},"status_not":{"anyOf":[{"items":{"$ref":"#/components/schemas/WorkflowStatus"},"type":"array"},{"type":"null"}],"title":"Status Not"},"assigned_to":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Assigned To"},"assigned":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Assigned"},"reviewer_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Reviewer Id"},"rework_count_min":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Rework Count Min"},"tags":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Tags"},"tags_none":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Tags None"},"tagged":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Tagged"},"classes":{"anyOf":[{"items":{"type":"string","format":"uuid"},"type":"array"},{"type":"null"}],"title":"Classes"},"classes_none":{"anyOf":[{"items":{"type":"string","format":"uuid"},"type":"array"},{"type":"null"}],"title":"Classes None"},"annotated":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Annotated"},"source":{"anyOf":[{"items":{"$ref":"#/components/schemas/AnnotationSource"},"type":"array"},{"type":"null"}],"title":"Source"},"source_none":{"anyOf":[{"items":{"$ref":"#/components/schemas/AnnotationSource"},"type":"array"},{"type":"null"}],"title":"Source None"},"created_at_from":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Created At From"},"created_at_to":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Created At To"},"uploaded_by":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Uploaded By"},"q":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Q"}},"additionalProperties":false,"type":"object","title":"ProjectFilter","description":"Superset of AssetFilter for project-scoped ops. Workflow + tag fields\nlive on `project_asset_state` rather than the asset row itself."},"ProjectGuidelineResponse":{"properties":{"content_md":{"type":"string","title":"Content Md","description":"Stored guideline Markdown with image URLs re-signed to fresh 1-hour presigned GETs."},"updated_by":{"anyOf":[{"$ref":"#/components/schemas/UserSummary"},{"type":"null"}],"description":"User who last updated the guideline, if known."},"created_at":{"type":"string","format":"date-time","title":"Created At","description":"When the guideline was first created."},"updated_at":{"type":"string","format":"date-time","title":"Updated At","description":"When the guideline was last updated."}},"type":"object","required":["content_md","created_at","updated_at"],"title":"ProjectGuidelineResponse","description":"GET /guideline — what every project member sees.\n\n`content_md` is the stored Markdown with any `guidelines/{pid}/...`\nimage URLs re-signed to fresh 1-hour TTL presigned-GET URLs so the\nlabeler's browser can render them without direct S3 access."},"ProjectGuidelineUpdate":{"properties":{"content_md":{"type":"string","maxLength":1048576,"title":"Content Md","description":"Markdown source (CommonMark + GFM). 1 MB cap — over → 422 here, 413 on the route. Image URLs in the doc should point at `guidelines/{project_id}/...` from this project's upload-init flow."}},"type":"object","required":["content_md"],"title":"ProjectGuidelineUpdate","description":"PUT /guideline body. Manager-only on the route."},"ProjectPage":{"properties":{"items":{"items":{"$ref":"#/components/schemas/ProjectResponse"},"type":"array","title":"Items"},"next_cursor":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Next Cursor"},"total":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Total"}},"type":"object","required":["items"],"title":"ProjectPage"},"ProjectProgress":{"properties":{"total":{"type":"integer","title":"Total"},"to_label":{"type":"integer","title":"To Label"},"in_progress":{"type":"integer","title":"In Progress"},"in_review":{"type":"integer","title":"In Review"},"done":{"type":"integer","title":"Done"},"skipped":{"type":"integer","title":"Skipped","default":0}},"type":"object","required":["total","to_label","in_progress","in_review","done"],"title":"ProjectProgress","description":"Counters from project_asset_state for a project's dashboard."},"ProjectResponse":{"properties":{"id":{"type":"string","format":"uuid","title":"Id","description":"Project id."},"dataset_id":{"type":"string","format":"uuid","title":"Dataset Id","description":"Id of the dataset this project labels."},"dataset_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Dataset Name","description":"Dataset name; populated only when the caller JOINed the dataset."},"dataset_kind":{"$ref":"#/components/schemas/DatasetKind","description":"Kind of the backing dataset (image or video)."},"name":{"type":"string","title":"Name","description":"Project name."},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description","description":"Optional project description."},"assignment_mode":{"$ref":"#/components/schemas/AssignmentMode","description":"How assets are assigned to labelers (self_claim or manual)."},"status":{"$ref":"#/components/schemas/ProjectStatus","description":"Project lifecycle status. User-facing reads only ever see `active` — a deleted project is tombstoned `deleting` and immediately excluded from all reads while its cascade runs in the background (ADR-0120)."},"tag_config":{"$ref":"#/components/schemas/TagConfig","description":"Per-project tag vocabulary (ADR-0050)."},"progress":{"anyOf":[{"$ref":"#/components/schemas/ProjectProgress"},{"type":"null"}],"description":"Workflow counters for dashboard cards; opt-in (expensive on hot writes)."},"created_at":{"type":"string","format":"date-time","title":"Created At","description":"Creation timestamp."},"updated_at":{"type":"string","format":"date-time","title":"Updated At","description":"Last-update timestamp."}},"type":"object","required":["id","dataset_id","dataset_kind","name","description","assignment_mode","status","tag_config","created_at","updated_at"],"title":"ProjectResponse"},"ProjectRole":{"type":"string","enum":["manager","reviewer","labeler"],"title":"ProjectRole"},"ProjectScope":{"properties":{"asset_ids":{"anyOf":[{"items":{"type":"string","format":"uuid"},"type":"array"},{"type":"null"}],"title":"Asset Ids"},"filter":{"anyOf":[{"$ref":"#/components/schemas/AssetFilter"},{"type":"null"}]},"all":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"All"}},"type":"object","title":"ProjectScope","description":"Initial asset selection for a project. Exactly one of the three fields."},"ProjectStatus":{"type":"string","enum":["active","deleting"],"title":"ProjectStatus"},"ProjectTagCreateRequest":{"properties":{"value":{"type":"string","maxLength":64,"minLength":1,"title":"Value"},"color":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Color"},"display":{"anyOf":[{"type":"string","maxLength":128,"minLength":1},{"type":"null"}],"title":"Display"}},"type":"object","required":["value"],"title":"ProjectTagCreateRequest","description":"Define a new palette tag. 409 if the value already exists."},"ProjectTagDeleteResponse":{"properties":{"value":{"type":"string","title":"Value","description":"The deleted tag value."},"assets_untagged":{"type":"integer","title":"Assets Untagged","description":"Number of assets the tag was removed from (0 if it was unused)."},"removed_from_vocab":{"type":"boolean","title":"Removed From Vocab","description":"True if the tag was also removed from tag_config.allowed_tags (the palette)."}},"type":"object","required":["value","assets_untagged","removed_from_vocab"],"title":"ProjectTagDeleteResponse","description":"Result of deleting a tag from a project (untag-all + palette cleanup)."},"ProjectTagListItem":{"properties":{"value":{"type":"string","title":"Value"},"color":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Color"},"display":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Display"},"count":{"type":"integer","title":"Count","description":"Assets in the project carrying this tag (0 = defined but unused)."}},"type":"object","required":["value","count"],"title":"ProjectTagListItem","description":"One tag in the project's authoritative palette (ADR-0058): its definition\n(value + color/display) plus current usage count."},"ProjectTagSummaryItem":{"properties":{"value":{"type":"string","title":"Value"},"count":{"type":"integer","title":"Count"}},"type":"object","required":["value","count"],"title":"ProjectTagSummaryItem","description":"One tag value + the count of assets in the target set that carry it.\nFE renders tri-state checkboxes from this:\n - count == total → checked\n - 0 < count < total → indeterminate\n - tag absent from list → unchecked"},"ProjectTagSummaryResponse":{"properties":{"total":{"type":"integer","title":"Total"},"tags":{"items":{"$ref":"#/components/schemas/ProjectTagSummaryItem"},"type":"array","title":"Tags"}},"type":"object","required":["total","tags"],"title":"ProjectTagSummaryResponse","description":"Aggregate tag application across a target set. Powers the unified\nbulk-tag editor (one checkbox grid replacing the dual-arm add/remove\npickers). Reuses the same `target` shape as `bulk-update-tags` so the\ndialog can pass the same payload it's about to act on.\n\nTags applied to **zero** assets in the target set are simply absent from\nthe response. Color / display metadata is **not** denormalized here —\nthe FE reads those from `project.tag_config.allowed_tags` by `value`.\nFree-form tag values (not in `allowed_tags`) show up with no color."},"ProjectTagUpdateRequest":{"properties":{"value":{"type":"string","maxLength":64,"minLength":1,"title":"Value","description":"Existing tag to update."},"new_value":{"anyOf":[{"type":"string","maxLength":64,"minLength":1},{"type":"null"}],"title":"New Value","description":"Rename target."},"color":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Color"},"display":{"anyOf":[{"type":"string","maxLength":128},{"type":"null"}],"title":"Display"}},"type":"object","required":["value"],"title":"ProjectTagUpdateRequest","description":"Rename and/or recolor/redisplay a palette tag. `value` identifies the\nexisting tag; supply at least one of `new_value` / `color` / `display`.\nRenaming rewrites every asset's tag array (merges if `new_value` already\nexists)."},"ProjectTagUpdateResponse":{"properties":{"value":{"type":"string","title":"Value"},"color":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Color"},"display":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Display"},"assets_renamed":{"type":"integer","title":"Assets Renamed","description":"Asset rows whose tag array was rewritten (rename only)."}},"type":"object","required":["value","assets_renamed"],"title":"ProjectTagUpdateResponse","description":"Result of a tag update: the final palette entry + how many assets were\nrewritten (rename only; 0 for a pure recolor)."},"ProjectTagsResponse":{"properties":{"tags":{"items":{"$ref":"#/components/schemas/ProjectTagListItem"},"type":"array","title":"Tags"}},"type":"object","required":["tags"],"title":"ProjectTagsResponse","description":"The project's authoritative tag palette (ADR-0058), each entry with its\ncolor/display and usage count. (Was a bare `list[str]` before v2.)"},"ProjectUpdate":{"properties":{"name":{"anyOf":[{"type":"string","maxLength":255,"minLength":1},{"type":"null"}],"title":"Name"},"description":{"anyOf":[{"type":"string","maxLength":1000},{"type":"null"}],"title":"Description"},"assignment_mode":{"anyOf":[{"$ref":"#/components/schemas/AssignmentMode"},{"type":"null"}]},"tag_config":{"anyOf":[{"$ref":"#/components/schemas/TagConfig"},{"type":"null"}]}},"type":"object","title":"ProjectUpdate"},"QueueStatsResponse":{"properties":{"all":{"type":"integer","title":"All","description":"Total in-scope tasks: to_label + rework + continue."},"to_label":{"type":"integer","title":"To Label","description":"Tasks in `to_label` (the New card)."},"rework":{"type":"integer","title":"Rework","description":"`in_progress` tasks with `rework_count > 0` (Rework card)."},"continue":{"type":"integer","title":"Continue","description":"`in_progress` tasks with `rework_count = 0` (Continue card)."}},"type":"object","required":["all","to_label","rework","continue"],"title":"QueueStatsResponse","description":"Bucket counts for the labeler's 4-card hero (ADR-0029)."},"RefreshRequest":{"properties":{"refresh_token":{"type":"string","minLength":1,"title":"Refresh Token","description":"A refresh token from /auth/callback or a prior /auth/refresh."}},"type":"object","required":["refresh_token"],"title":"RefreshRequest"},"RejectRequest":{"properties":{"reason":{"anyOf":[{"type":"string","maxLength":1000},{"type":"null"}],"title":"Reason"}},"type":"object","title":"RejectRequest"},"RevisionAnnotation":{"properties":{"annotation_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Annotation Id","description":"Client-owned ID of the annotation, or null if the annotation was hard-deleted."},"class_id":{"type":"string","format":"uuid","title":"Class Id","description":"ID of the annotation class this shape belongs to."},"type":{"$ref":"#/components/schemas/AnnotationType","description":"Kind of annotation (bbox, polygon, keypoint, classification)."},"geometry":{"anyOf":[{"oneOf":[{"$ref":"#/components/schemas/BboxGeometryRead"},{"$ref":"#/components/schemas/PolygonGeometryRead"},{"$ref":"#/components/schemas/KeypointGeometryRead"}],"discriminator":{"propertyName":"type","mapping":{"bbox":"#/components/schemas/BboxGeometryRead","keypoint":"#/components/schemas/KeypointGeometryRead","polygon":"#/components/schemas/PolygonGeometryRead"}}},{"type":"null"}],"title":"Geometry","description":"Typed geometry snapshot (read union, ADR-0104); null for classification annotations."},"data":{"additionalProperties":true,"type":"object","title":"Data","description":"Class-attribute values and other annotation metadata."},"instance_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Instance Id","description":"ID linking this annotation to a tracked instance, or null if unlinked."}},"type":"object","required":["annotation_id","class_id","type","geometry","data","instance_id"],"title":"RevisionAnnotation","description":"One annotation's state as captured in a commit."},"RevisionDetail":{"properties":{"submit_seq":{"type":"integer","title":"Submit Seq","description":"Monotonic sequence number of this commit within the asset's history."},"reason":{"$ref":"#/components/schemas/RevisionReason","description":"Why this revision was created (e.g. submitted, approved, rejected)."},"actor_user_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Actor User Id","description":"ID of the user who made this commit, or null if not attributable to a user."},"actor_role":{"anyOf":[{"$ref":"#/components/schemas/ProjectRole"},{"type":"null"}],"description":"The committing user's effective project role at commit time, or null."},"committed_at":{"type":"string","format":"date-time","title":"Committed At","description":"Timestamp when this revision was committed."},"annotations":{"items":{"$ref":"#/components/schemas/RevisionAnnotation"},"type":"array","title":"Annotations","description":"Full annotation snapshot as it existed at this commit."}},"type":"object","required":["submit_seq","reason","actor_user_id","actor_role","committed_at","annotations"],"title":"RevisionDetail","description":"A single commit + the full annotation snapshot at that point — what the\neditor renders to view or diff a past state."},"RevisionReason":{"type":"string","enum":["submitted","approved","rejected"],"title":"RevisionReason"},"RevisionSummary":{"properties":{"submit_seq":{"type":"integer","title":"Submit Seq","description":"Monotonic sequence number of this commit within the asset's history."},"reason":{"$ref":"#/components/schemas/RevisionReason","description":"Why this revision was created (e.g. submitted, approved, rejected)."},"actor_user_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Actor User Id","description":"ID of the user who made this commit, or null if not attributable to a user."},"actor_role":{"anyOf":[{"$ref":"#/components/schemas/ProjectRole"},{"type":"null"}],"description":"The committing user's effective project role at commit time, or null."},"committed_at":{"type":"string","format":"date-time","title":"Committed At","description":"Timestamp when this revision was committed."},"annotation_count":{"type":"integer","title":"Annotation Count","description":"Number of annotations captured in this commit's snapshot."}},"type":"object","required":["submit_seq","reason","actor_user_id","actor_role","committed_at","annotation_count"],"title":"RevisionSummary","description":"One commit in an asset's history (the editor's revision list)."},"RevisionSummaryPage":{"properties":{"items":{"items":{"$ref":"#/components/schemas/RevisionSummary"},"type":"array","title":"Items","description":"This page of revision summaries."},"next_cursor":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Next Cursor","description":"Opaque cursor for the next page, or null when this is the last page."}},"type":"object","required":["items"],"title":"RevisionSummaryPage","description":"A page of an asset's commit history (ADR-0012 cursor pagination)."},"SearchHit":{"properties":{"id":{"type":"string","format":"uuid","title":"Id","description":"Asset UUID."},"filename":{"type":"string","title":"Filename","description":"Original asset filename."},"similarity":{"type":"number","title":"Similarity"},"content_type":{"type":"string","title":"Content Type","description":"MIME type of the asset."},"status":{"$ref":"#/components/schemas/AssetStatus","description":"Asset processing status."},"properties":{"$ref":"#/components/schemas/AssetProperties","description":"Extracted asset properties (dimensions, format, EXIF, etc.)."},"thumbnail_url":{"anyOf":[{"$ref":"#/components/schemas/AssetDownloadUrl"},{"type":"null"}],"description":"Presigned GET to the 320px thumbnail. Present only with ?include=thumbnail_url."},"image_url":{"anyOf":[{"$ref":"#/components/schemas/AssetDownloadUrl"},{"type":"null"}],"description":"Presigned GET to the full-res asset bytes. Present only with ?include=image_url."}},"type":"object","required":["id","filename","similarity","content_type","status","properties"],"title":"SearchHit","description":"One result row. `similarity` is in [0, 1] (cosine; 1 = identical)."},"SearchQuery":{"properties":{"parts":{"items":{"$ref":"#/components/schemas/SearchQueryPart"},"type":"array","maxItems":8,"minItems":1,"title":"Parts"},"instruction":{"anyOf":[{"type":"string","maxLength":512},{"type":"null"}],"title":"Instruction"}},"type":"object","required":["parts"],"title":"SearchQuery"},"SearchQueryPart":{"properties":{"type":{"type":"string","enum":["text","image","asset_id"],"title":"Type"},"text":{"anyOf":[{"type":"string","maxLength":4096},{"type":"null"}],"title":"Text"},"image_uri":{"anyOf":[{"type":"string","maxLength":2048},{"type":"null"}],"title":"Image Uri"},"asset_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Asset Id"}},"type":"object","required":["type"],"title":"SearchQueryPart","description":"One slice of a multimodal query. Exactly one of `text`, `image_uri`, or\n`asset_id` must be set per part, matching the declared `type`."},"SearchRequest":{"properties":{"query":{"$ref":"#/components/schemas/SearchQuery"},"filters":{"anyOf":[{"$ref":"#/components/schemas/AssetFilter"},{"type":"null"}]},"k":{"type":"integer","maximum":200.0,"minimum":1.0,"title":"K","default":50},"include_total":{"type":"boolean","title":"Include Total","default":false}},"type":"object","required":["query"],"title":"SearchRequest"},"SearchResponse":{"properties":{"items":{"items":{"$ref":"#/components/schemas/SearchHit"},"type":"array","title":"Items"},"total":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Total"}},"type":"object","required":["items"],"title":"SearchResponse"},"SegmentPoint":{"properties":{"x":{"type":"number","title":"X","description":"Click x in ORIGINAL image pixels."},"y":{"type":"number","title":"Y","description":"Click y in ORIGINAL image pixels."},"label":{"type":"integer","maximum":1.0,"minimum":0.0,"title":"Label","description":"1 = positive (include this region), 0 = negative (exclude). Refine a mask by adding negative clicks on over-segmented areas, positive clicks to grow it.","default":1}},"type":"object","required":["x","y"],"title":"SegmentPoint"},"SegmentRequest":{"properties":{"points":{"items":{"$ref":"#/components/schemas/SegmentPoint"},"type":"array","maxItems":64,"title":"Points","description":"Click prompts. Positive/negative points that seed and refine the mask."},"box":{"anyOf":[{"items":{"type":"number"},"type":"array"},{"type":"null"}],"title":"Box","description":"Optional bounding-box prompt [x1, y1, x2, y2] in ORIGINAL image pixels."}},"type":"object","title":"SegmentRequest"},"SegmentResponse":{"properties":{"suggestions":{"items":{"$ref":"#/components/schemas/SegmentSuggestion"},"type":"array","title":"Suggestions","description":"Zero or one suggestion (SAM returns the best mask for the prompt). Not persisted — the labeler assigns a class and commits via the normal annotation create."},"inference_ms":{"type":"integer","title":"Inference Ms","description":"Server-side SAM inference time in milliseconds."}},"type":"object","required":["suggestions","inference_ms"],"title":"SegmentResponse"},"SegmentSuggestion":{"properties":{"type":{"$ref":"#/components/schemas/OutputGeometry","description":"Geometry kind SAM emitted for this suggestion.","default":"polygon"},"geometry":{"oneOf":[{"$ref":"#/components/schemas/BboxGeometryRead"},{"$ref":"#/components/schemas/PolygonGeometryRead"},{"$ref":"#/components/schemas/KeypointGeometryRead"}],"title":"Geometry","description":"Typed geometry wrapping SAM's flat polygon output (ADR-0091/0104) — the same shape `annotation.geometry` stores, so the FE commits an accepted suggestion through the normal annotation-create path unchanged.","discriminator":{"propertyName":"type","mapping":{"bbox":"#/components/schemas/BboxGeometryRead","keypoint":"#/components/schemas/KeypointGeometryRead","polygon":"#/components/schemas/PolygonGeometryRead"}}},"score":{"type":"number","title":"Score","description":"SAM's confidence for this mask."}},"type":"object","required":["geometry","score"],"title":"SegmentSuggestion"},"SkippedDetail":{"properties":{"asset_id":{"type":"string","title":"Asset Id"},"annotation_id":{"anyOf":[{"type":"integer"},{"type":"string"}],"title":"Annotation Id"},"annotation_type":{"type":"string","title":"Annotation Type"},"reason":{"type":"string","title":"Reason"}},"type":"object","required":["asset_id","annotation_id","annotation_type","reason"],"title":"SkippedDetail","description":"One annotation the bundler couldn't represent (persisted serialization of\n`FormatterSkip`). `asset_id` is stringified on write; `annotation_id` is int\n(COCO/YOLO) or str (delta/manifest-miss sentinel)."},"SplitConfig":{"properties":{"strategy":{"$ref":"#/components/schemas/SplitStrategy","description":"Train/val/test split strategy.","default":"random"},"train":{"type":"number","exclusiveMaximum":1.0,"exclusiveMinimum":0.0,"title":"Train","description":"Train-split ratio (0,1).","default":0.8},"val":{"type":"number","exclusiveMaximum":1.0,"minimum":0.0,"title":"Val","description":"Validation-split ratio [0,1).","default":0.1},"test":{"type":"number","exclusiveMaximum":1.0,"minimum":0.0,"title":"Test","description":"Test-split ratio [0,1).","default":0.1},"seed":{"type":"integer","title":"Seed","description":"Seed for the deterministic ratio split.","default":42},"train_tag":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Train Tag","description":"Manual split (ADR-0065): frozen tag marking the train split; null falls back to a seeded ratio split."},"val_tag":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Val Tag","description":"Manual split (ADR-0065): frozen tag marking the val split; null falls back to a seeded ratio split."},"test_tag":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Test Tag","description":"Manual split (ADR-0065): frozen tag marking the test split; null falls back to a seeded ratio split."}},"type":"object","title":"SplitConfig"},"SplitStrategy":{"type":"string","enum":["random","stratified","manual"],"title":"SplitStrategy","description":"Train/val/test split strategy (stored in training-run options JSONB)."},"TagConfig":{"properties":{"free_form":{"type":"boolean","title":"Free Form","default":true},"allowed_tags":{"items":{"$ref":"#/components/schemas/TagConfigEntry"},"type":"array","maxItems":1000,"title":"Allowed Tags"}},"type":"object","title":"TagConfig","description":"Per-project tag vocabulary (ADR-0050). `free_form=true` (default) means\nthe server doesn't enforce; `allowed_tags` is a cosmetic palette.\n`free_form=false` rejects unknown tags at bulk-add-tags time."},"TagConfigEntry":{"properties":{"value":{"type":"string","maxLength":64,"minLength":1,"title":"Value"},"color":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Color"},"display":{"anyOf":[{"type":"string","maxLength":128,"minLength":1},{"type":"null"}],"title":"Display"}},"type":"object","required":["value"],"title":"TagConfigEntry","description":"One entry in `tag_config.allowed_tags` on a Project. `value` is the\ncanonical wire form; `display`/`color` are FE rendering hints."},"TenantCreate":{"properties":{"slug":{"type":"string","maxLength":30,"minLength":3,"title":"Slug","description":"URL-safe unique identifier for the tenant (lowercase alphanumeric with single hyphens)."},"name":{"type":"string","maxLength":255,"minLength":1,"title":"Name","description":"Human-readable display name of the tenant."},"owner":{"$ref":"#/components/schemas/OwnerCreate","description":"Details of the initial owner account created alongside the tenant."}},"type":"object","required":["slug","name","owner"],"title":"TenantCreate"},"TenantCreateMe":{"properties":{"name":{"type":"string","maxLength":255,"minLength":1,"title":"Name","description":"Display name."},"slug":{"type":"string","maxLength":30,"minLength":3,"title":"Slug","description":"URL handle (lowercase alphanumeric with single hyphens); unique platform-wide."}},"type":"object","required":["name","slug"],"title":"TenantCreateMe","description":"Body for `POST /me/tenants` — create a workspace as the authenticated\naccount (who becomes its admin). No owner block: the account exists."},"TenantMembership":{"properties":{"tenant_id":{"type":"string","format":"uuid","title":"Tenant Id","description":"Tenant id (stable reference)."},"slug":{"type":"string","title":"Slug","description":"URL handle — routes into `/tenants/{slug}/…`."},"name":{"type":"string","title":"Name","description":"Display name of the workspace."},"role":{"$ref":"#/components/schemas/WorkspaceRole","description":"This account's workspace role here."},"joined_at":{"type":"string","format":"date-time","title":"Joined At","description":"When this membership was created."}},"type":"object","required":["tenant_id","slug","name","role","joined_at"],"title":"TenantMembership","description":"One workspace this account belongs to (`GET /me/tenants` item, and the\nresult of accepting an invitation)."},"TenantResponse":{"properties":{"id":{"type":"string","format":"uuid","title":"Id","description":"Unique identifier of the tenant."},"slug":{"type":"string","title":"Slug","description":"URL-safe unique identifier for the tenant."},"name":{"type":"string","title":"Name","description":"Human-readable display name of the tenant."},"created_at":{"type":"string","format":"date-time","title":"Created At","description":"Timestamp when the tenant was created."},"owner":{"$ref":"#/components/schemas/OwnerResponse","description":"The tenant's owner user."}},"type":"object","required":["id","slug","name","created_at","owner"],"title":"TenantResponse"},"TrainedModelListPage":{"properties":{"items":{"items":{"$ref":"#/components/schemas/TrainedModelResponse"},"type":"array","title":"Items"},"next_cursor":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Next Cursor"}},"type":"object","required":["items"],"title":"TrainedModelListPage"},"TrainedModelResponse":{"properties":{"id":{"type":"string","format":"uuid","title":"Id","description":"Trained-model id."},"name":{"type":"string","title":"Name","description":"Display name (unique per tenant, case-insensitive)."},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description","description":"Optional free-text description; null if unset."},"task":{"$ref":"#/components/schemas/TrainingTask","description":"Task the model performs (detection or segmentation)."},"model":{"type":"string","title":"Model","description":"Catalog model key the run used (e.g. 'rf-detr-nano')."},"project_id":{"type":"string","format":"uuid","title":"Project Id","description":"Lineage: which project's data produced this model (tenant registry)."},"training_run_id":{"type":"string","format":"uuid","title":"Training Run Id","description":"Training run that produced this model."},"project_version_id":{"type":"string","format":"uuid","title":"Project Version Id","description":"Frozen project version the run trained on."},"class_map":{"additionalProperties":{"type":"string"},"type":"object","title":"Class Map","description":"Model class index → class name."},"metrics_summary":{"$ref":"#/components/schemas/TrainingMetricsSummary","description":"Summary evaluation metrics for the trained model."},"created_at":{"type":"string","format":"date-time","title":"Created At","description":"When the model was registered."},"updated_at":{"type":"string","format":"date-time","title":"Updated At","description":"When the model was last updated."}},"type":"object","required":["id","name","description","task","model","project_id","training_run_id","project_version_id","class_map","metrics_summary","created_at","updated_at"],"title":"TrainedModelResponse"},"TrainedModelUpdate":{"properties":{"name":{"anyOf":[{"type":"string","maxLength":255},{"type":"null"}],"title":"Name","description":"New display name (unique per tenant, case-insensitive); omit to leave unchanged."},"description":{"anyOf":[{"type":"string","maxLength":1000},{"type":"null"}],"title":"Description","description":"New description; send null to clear it, omit to leave unchanged."}},"type":"object","title":"TrainedModelUpdate","description":"Edit a trained model (ADR-0072) — name and/or description (partial; send only\nwhat changes). Name is unique per tenant (case-insensitive); `description` may be\nset to null to clear it."},"TrainingMetricPoint":{"properties":{"epoch":{"type":"integer","title":"Epoch","description":"Epoch index for this point on the curve."},"train_loss":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Train Loss","description":"Training loss this epoch; null if unavailable."},"val_loss":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Val Loss","description":"Validation loss this epoch; null if there is no val split."},"val_map":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Val Map","description":"Validation mAP@[.50:.95]; null if no val split."},"val_map_50":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Val Map 50","description":"Validation mAP@.50; null if no val split."},"val_precision":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Val Precision","description":"Validation precision this epoch; null if no val split."},"val_recall":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Val Recall","description":"Validation recall this epoch; null if no val split."}},"type":"object","required":["epoch","train_loss","val_loss","val_map","val_map_50","val_precision","val_recall"],"title":"TrainingMetricPoint","description":"One epoch on the live training curve (ADR-0064). Losses fall out of the\nloop each epoch; the val accuracy metrics come from a per-epoch eval pass on\nthe val split. Any field may be null (e.g. a run with no val split → losses\nonly)."},"TrainingMetricsResponse":{"properties":{"items":{"items":{"$ref":"#/components/schemas/TrainingMetricPoint"},"type":"array","title":"Items"}},"type":"object","required":["items"],"title":"TrainingMetricsResponse"},"TrainingMetricsSummary":{"properties":{"optimal_confidence":{"type":"number","title":"Optimal Confidence","default":0.0},"overall":{"$ref":"#/components/schemas/OverallMetrics"},"per_class":{"items":{"$ref":"#/components/schemas/PerClassMetrics"},"type":"array","title":"Per Class"},"confusion_matrix":{"anyOf":[{"$ref":"#/components/schemas/ConfusionMatrix"},{"type":"null"}]},"num_test":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Num Test"}},"type":"object","title":"TrainingMetricsSummary","description":"The full scorecard persisted to `TrainedModel.metrics_summary` and echoed\non `TrainedModelResponse.metrics_summary`. Lenient on read (`extra=\"ignore\"`)\nso a future added key never 500s a model read; `num_test` is set only on the\nfinal (full) scorecard, so it is Optional."},"TrainingRunCreate":{"properties":{"name":{"type":"string","maxLength":255,"minLength":1,"title":"Name","description":"Display name for the training run."},"task":{"$ref":"#/components/schemas/TrainingTask","description":"Task the run trains for (detection or segmentation)."},"model":{"type":"string","title":"Model","description":"Catalog model key (e.g. 'rf-detr-nano')."},"epochs":{"anyOf":[{"type":"integer","maximum":300.0,"minimum":1.0},{"type":"null"}],"title":"Epochs","description":"Number of training epochs; null uses the catalog default."},"early_stopping":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Early Stopping","description":"Stop early on val-loss plateau; null uses the catalog default."},"patience":{"anyOf":[{"type":"integer","minimum":1.0},{"type":"null"}],"title":"Patience","description":"Epochs without improvement before early-stop; null uses the catalog default."},"split":{"anyOf":[{"$ref":"#/components/schemas/SplitConfig"},{"type":"null"}],"description":"Train/val/test split config; null uses the catalog default."}},"type":"object","required":["name","task","model"],"title":"TrainingRunCreate"},"TrainingRunListPage":{"properties":{"items":{"items":{"$ref":"#/components/schemas/TrainingRunResponse"},"type":"array","title":"Items"},"next_cursor":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Next Cursor"}},"type":"object","required":["items"],"title":"TrainingRunListPage"},"TrainingRunResponse":{"properties":{"id":{"type":"string","format":"uuid","title":"Id","description":"Training-run id."},"project_version_id":{"type":"string","format":"uuid","title":"Project Version Id","description":"Frozen project version this run trains on."},"name":{"type":"string","title":"Name","description":"Display name for the training run."},"task":{"$ref":"#/components/schemas/TrainingTask","description":"Task the run trains for (detection or segmentation)."},"model":{"type":"string","title":"Model","description":"Catalog model key (e.g. 'rf-detr-nano')."},"status":{"$ref":"#/components/schemas/TrainingRunStatus","description":"Current run status."},"epochs":{"type":"integer","title":"Epochs","description":"Configured number of training epochs."},"epochs_completed":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Epochs Completed","description":"Epochs finished so far; null before training starts."},"split":{"$ref":"#/components/schemas/SplitConfig","description":"Resolved train/val/test split config for this run."},"error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error","description":"Failure message when status is failed; else null."},"created_at":{"type":"string","format":"date-time","title":"Created At","description":"When the run was created."},"started_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Started At","description":"When training started; null while queued."},"completed_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Completed At","description":"When the run finished; null until terminal."}},"type":"object","required":["id","project_version_id","name","task","model","status","epochs","epochs_completed","split","error","created_at","started_at","completed_at"],"title":"TrainingRunResponse"},"TrainingRunStatus":{"type":"string","enum":["queued","running","succeeded","failed","canceled"],"title":"TrainingRunStatus"},"TrainingStatsClass":{"properties":{"class_id":{"type":"string","format":"uuid","title":"Class Id","description":"Class id."},"name":{"type":"string","title":"Name","description":"Class display name."},"instance_count":{"type":"integer","title":"Instance Count","description":"Total annotations of this class (of the task geometry)."},"image_count":{"type":"integer","title":"Image Count","description":"Distinct images containing this class."}},"type":"object","required":["class_id","name","instance_count","image_count"],"title":"TrainingStatsClass"},"TrainingStatsResponse":{"properties":{"task":{"$ref":"#/components/schemas/TrainingTask","description":"Task these stats are computed for."},"images_total":{"type":"integer","title":"Images Total","description":"Frozen images in the version."},"images_trainable":{"type":"integer","title":"Images Trainable","description":"Detection: all (empties = negatives); segmentation: images with >=1 mask."},"images_empty":{"type":"integer","title":"Images Empty","description":"Images with no annotation of the task geometry."},"instances_total":{"type":"integer","title":"Instances Total","description":"Total annotation instances of the task geometry."},"classes":{"items":{"$ref":"#/components/schemas/TrainingStatsClass"},"type":"array","title":"Classes","description":"Per-class instance and image counts."}},"type":"object","required":["task","images_total","images_trainable","images_empty","instances_total","classes"],"title":"TrainingStatsResponse","description":"Pre-train readiness for one task on a frozen version (ADR-0064). The FE\nrenders this and raises its own advisory warnings; the BE only hard-blocks\ngenuine impossibility at create time."},"TrainingTask":{"type":"string","enum":["detection","segmentation"],"title":"TrainingTask"},"UnifiedDatasetPage":{"properties":{"items":{"items":{"$ref":"#/components/schemas/UnifiedDatasetResponse"},"type":"array","title":"Items"},"next_cursor":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Next Cursor"},"total":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Total"}},"type":"object","required":["items"],"title":"UnifiedDatasetPage"},"UnifiedDatasetResponse":{"properties":{"id":{"type":"string","format":"uuid","title":"Id","description":"Server-generated unique identifier of the dataset."},"name":{"type":"string","title":"Name","description":"Human-readable display name of the dataset."},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description","description":"Optional free-text description of the dataset; null if unset."},"created_by":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Created By","description":"Identifier of the user who created the dataset; null if unknown or system-created."},"created_at":{"type":"string","format":"date-time","title":"Created At","description":"UTC timestamp when the dataset was created."},"kind":{"type":"string","const":"image","title":"Kind","description":"Discriminator for the dataset kind; lets the FE route to the correct kind-specific endpoint."}},"type":"object","required":["id","name","description","created_by","created_at","kind"],"title":"UnifiedDatasetResponse","description":"Returned by the cross-kind list endpoint. `kind` discriminator lets the FE\nroute to the correct kind-specific endpoint for follow-up calls."},"UserPage":{"properties":{"items":{"items":{"$ref":"#/components/schemas/UserResponse"},"type":"array","title":"Items"},"next_cursor":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Next Cursor"}},"type":"object","required":["items"],"title":"UserPage"},"UserResponse":{"properties":{"id":{"type":"string","format":"uuid","title":"Id"},"email":{"type":"string","title":"Email"},"first_name":{"type":"string","title":"First Name"},"last_name":{"type":"string","title":"Last Name"},"role":{"$ref":"#/components/schemas/WorkspaceRole"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"updated_at":{"type":"string","format":"date-time","title":"Updated At"}},"type":"object","required":["id","email","first_name","last_name","role","created_at","updated_at"],"title":"UserResponse"},"UserRoleUpdate":{"properties":{"role":{"$ref":"#/components/schemas/WorkspaceRole"}},"type":"object","required":["role"],"title":"UserRoleUpdate","description":"Body for PATCH /users/{user_id}. Admin-only — changes the target\nuser's workspace role. Blocked by last-admin guard when demoting."},"UserSeedCreate":{"properties":{"email":{"type":"string","maxLength":254,"format":"email","title":"Email","description":"Email address of the account to create."},"first_name":{"type":"string","maxLength":100,"minLength":1,"title":"First Name","description":"Given name (no sign-up form runs on this path)."},"last_name":{"type":"string","maxLength":100,"minLength":1,"title":"Last Name","description":"Family name."},"password":{"type":"string","maxLength":128,"minLength":10,"title":"Password","description":"Initial password (WorkOS strength rules apply)."},"role":{"$ref":"#/components/schemas/WorkspaceRole","description":"Workspace role to grant.","default":"member"}},"type":"object","required":["email","first_name","last_name","password"],"title":"UserSeedCreate","description":"Body for `POST /users` — programmatic seeding ONLY (ADR-0113): create a\nready-to-login member directly (WorkOS account with password + membership,\nno email). Humans join via `/tenants/{slug}/invitations` + in-app accept."},"UserSelfUpdate":{"properties":{"first_name":{"anyOf":[{"type":"string","maxLength":100,"minLength":1},{"type":"null"}],"title":"First Name","description":"Updated given name. Omit to leave unchanged."},"last_name":{"anyOf":[{"type":"string","maxLength":100,"minLength":1},{"type":"null"}],"title":"Last Name","description":"Updated family name. Omit to leave unchanged."}},"type":"object","title":"UserSelfUpdate","description":"Edit your own profile (first/last name only). Email + password have their\nown dedicated endpoints."},"UserSummary":{"properties":{"id":{"type":"string","format":"uuid","title":"Id"},"email":{"type":"string","title":"Email"},"first_name":{"type":"string","title":"First Name"},"last_name":{"type":"string","title":"Last Name"}},"type":"object","required":["id","email","first_name","last_name"],"title":"UserSummary","description":"Compact user reference for embedding in other responses (members,\nasset_state, etc.) so the FE doesn't have to do N+1 lookups for names."},"ValidationError":{"properties":{"loc":{"items":{"anyOf":[{"type":"string"},{"type":"integer"}]},"type":"array","title":"Location"},"msg":{"type":"string","title":"Message"},"type":{"type":"string","title":"Error Type"},"input":{"title":"Input"},"ctx":{"type":"object","title":"Context"}},"type":"object","required":["loc","msg","type"],"title":"ValidationError"},"VersionAssetDetail":{"properties":{"asset_id":{"type":"string","format":"uuid","title":"Asset Id"},"filename":{"type":"string","title":"Filename"},"width":{"type":"integer","title":"Width"},"height":{"type":"integer","title":"Height"},"tags":{"items":{"type":"string"},"type":"array","title":"Tags"},"annotation_count":{"type":"integer","title":"Annotation Count"},"status":{"type":"string","enum":["present","version_only","purged"],"title":"Status"},"thumbnail_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Thumbnail Url"},"image_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Image Url"},"shapes":{"items":{"$ref":"#/components/schemas/FrozenShape"},"type":"array","title":"Shapes"}},"type":"object","required":["asset_id","filename","width","height","tags","annotation_count","status","thumbnail_url","image_url","shapes"],"title":"VersionAssetDetail","description":"Single frozen asset (`GET …/versions/{vid}/assets/{aid}`) — the browse view\nplus the frozen annotation shapes, read straight from the manifest so it\nresolves even for a `version_only` (deleted) asset."},"VersionAssetPage":{"properties":{"items":{"items":{"$ref":"#/components/schemas/VersionAssetView"},"type":"array","title":"Items"},"next_cursor":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Next Cursor"},"total":{"type":"integer","title":"Total"}},"type":"object","required":["items","total"],"title":"VersionAssetPage","description":"Page of a version's frozen assets (`GET …/versions/{vid}/assets`). Mirrors\n`AssetPage` — cursor-paginated, `total` is the count matching the q/tags\nfilter (the whole frozen set when unfiltered)."},"VersionAssetView":{"properties":{"asset_id":{"type":"string","format":"uuid","title":"Asset Id"},"filename":{"type":"string","title":"Filename"},"width":{"type":"integer","title":"Width"},"height":{"type":"integer","title":"Height"},"tags":{"items":{"type":"string"},"type":"array","title":"Tags"},"annotation_count":{"type":"integer","title":"Annotation Count"},"status":{"type":"string","enum":["present","version_only","purged"],"title":"Status"},"thumbnail_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Thumbnail Url"},"image_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Image Url"}},"type":"object","required":["asset_id","filename","width","height","tags","annotation_count","status","thumbnail_url","image_url"],"title":"VersionAssetView","description":"One asset as it was *frozen* in a version (ADR-0065 browser) — served from\nthe manifest, not the live row, so it survives the asset being deleted."},"VersionCreate":{"properties":{"name":{"type":"string","maxLength":255,"minLength":1,"title":"Name"},"description":{"anyOf":[{"type":"string","maxLength":1000},{"type":"null"}],"title":"Description"},"selection":{"anyOf":[{"$ref":"#/components/schemas/VersionSelection"},{"type":"null"}]}},"type":"object","required":["name"],"title":"VersionCreate"},"VersionPage":{"properties":{"items":{"items":{"$ref":"#/components/schemas/VersionResponse"},"type":"array","title":"Items"},"next_cursor":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Next Cursor"},"total":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Total"}},"type":"object","required":["items"],"title":"VersionPage"},"VersionResponse":{"properties":{"id":{"type":"string","format":"uuid","title":"Id","description":"Version id."},"project_id":{"type":"string","format":"uuid","title":"Project Id","description":"Id of the project this version snapshots."},"name":{"type":"string","title":"Name","description":"Version name."},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description","description":"Optional version description."},"status":{"$ref":"#/components/schemas/VersionStatus","description":"Build status of the version (pending, building, ready, or failed)."},"frozen_asset_count":{"type":"integer","title":"Frozen Asset Count","description":"Number of assets frozen into this version."},"frozen_class_count":{"type":"integer","title":"Frozen Class Count","description":"Number of classes frozen into this version."},"frozen_classes":{"$ref":"#/components/schemas/FrozenClassSet"},"error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error","description":"Failure message when status is failed."},"created_by":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Created By","description":"User who created the version, if known."},"created_at":{"type":"string","format":"date-time","title":"Created At","description":"Creation timestamp."},"updated_at":{"type":"string","format":"date-time","title":"Updated At","description":"Last-update timestamp."},"completed_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Completed At","description":"When the version build completed, if done."}},"type":"object","required":["id","project_id","name","description","status","frozen_asset_count","frozen_class_count","error","created_by","created_at","updated_at","completed_at"],"title":"VersionResponse"},"VersionSelection":{"properties":{"asset_ids":{"anyOf":[{"items":{"type":"string","format":"uuid"},"type":"array"},{"type":"null"}],"title":"Asset Ids"},"filter":{"anyOf":[{"$ref":"#/components/schemas/ProjectFilter"},{"type":"null"}]},"all":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"All"}},"type":"object","title":"VersionSelection","description":"Which of the project's in-scope assets to freeze into a version\n(ADR-0065 §4). Exactly one of the three; omit the whole `selection` (or use\n`all: true`) to freeze every in-scope asset (the default). `asset_ids` and\n`filter` are intersected with the project's in-scope, non-deleted assets."},"VersionStatus":{"type":"string","enum":["pending","building","ready","failed"],"title":"VersionStatus"},"VersionUpdate":{"properties":{"name":{"anyOf":[{"type":"string","maxLength":255},{"type":"null"}],"title":"Name"},"description":{"anyOf":[{"type":"string","maxLength":1000},{"type":"null"}],"title":"Description"}},"type":"object","title":"VersionUpdate","description":"Edit a version (ADR-0072) — name and/or description (partial; send only what\nchanges). Name is unique per project (case-insensitive); `description` may be set\nto null to clear it. Does not touch the frozen snapshot."},"VisibilityModel":{"type":"string","enum":["binary","coco_ternary"],"title":"VisibilityModel"},"WorkflowStatus":{"type":"string","enum":["to_label","in_progress","in_review","done","skipped"],"title":"WorkflowStatus"},"WorkspaceRole":{"type":"string","enum":["admin","member"],"title":"WorkspaceRole"},"YoloResultDetails":{"properties":{"format":{"type":"string","const":"yolo","title":"Format"},"num_images":{"type":"integer","title":"Num Images"},"num_labels":{"type":"integer","title":"Num Labels"},"num_classes":{"type":"integer","title":"Num Classes"},"classes":{"items":{"$ref":"#/components/schemas/ClassCountByIdx"},"type":"array","title":"Classes"},"classes_truncated":{"type":"boolean","title":"Classes Truncated","default":false},"classes_total":{"type":"integer","title":"Classes Total","default":0}},"type":"object","required":["format","num_images","num_labels","num_classes"],"title":"YoloResultDetails"},"ZeroPredictRequest":{"properties":{"image":{"$ref":"#/components/schemas/ImageRef","description":"The image to run prediction on."},"text_prompts":{"items":{"type":"string"},"type":"array","title":"Text Prompts","description":"Open-vocab class names to detect."},"box_prompts":{"items":{"$ref":"#/components/schemas/BoxPrompt"},"type":"array","title":"Box Prompts","description":"Few-shot visual example prompts."},"confidence":{"anyOf":[{"type":"number","maximum":1.0,"minimum":0.0},{"type":"null"}],"title":"Confidence","description":"One slider mapped to both ZERO thresholds."}},"type":"object","required":["image"],"title":"ZeroPredictRequest"},"ZeroPredictResponse":{"properties":{"predictions":{"items":{"$ref":"#/components/schemas/FoundationPrediction"},"type":"array","title":"Predictions","description":"Predictions for the image."},"image":{"$ref":"#/components/schemas/ImageSize","description":"Decoded image dimensions."},"inference_ms":{"type":"integer","title":"Inference Ms","description":"Server-side inference time in milliseconds."}},"type":"object","required":["predictions","image","inference_ms"],"title":"ZeroPredictResponse"},"ErrorCode":{"type":"string","enum":["UNCATEGORIZED","AUTH_MISSING","AUTH_INVALID_CREDENTIAL","AUTH_TOKEN_EXPIRED","AUTH_FORBIDDEN","AUTH_TENANT_MISMATCH","VALIDATION_ERROR","VALIDATION_REQUIRED_CLASSIFICATION_MISSING","BAD_REQUEST","INVALID_CURSOR","INVALID_FILTER","UNSUPPORTED_OPERATION","PAYLOAD_TOO_LARGE","TENANT_NOT_FOUND","TENANT_SLUG_TAKEN","USER_NOT_FOUND","USER_EMAIL_TAKEN","INVITATION_NOT_FOUND","INVITATION_PENDING","INVITATION_NOT_PENDING","INVITATION_EXPIRED","LAST_ADMIN","DATASET_NOT_FOUND","DATASET_NAME_TAKEN","DATASET_DELETED","DATASET_ASSET_CAP_EXCEEDED","ASSET_NOT_FOUND","ASSET_DELETED","ASSET_NOT_EMBEDDED","ASSET_FORMAT_UNSUPPORTED","ASSET_TOO_LARGE","PROJECT_NOT_FOUND","PROJECT_ARCHIVED","PROJECT_MEMBER_NOT_FOUND","PROJECT_CLASS_NOT_FOUND","VERSION_NOT_FOUND","MODEL_NOT_FOUND","RESOURCE_NOT_READY","PROJECT_STATE_FORBIDDEN","CLASS_LOCKED","CLASS_IN_USE","DEPLOYMENT_NOT_FOUND","DEPLOYMENT_STATE_FORBIDDEN","EXPORT_EXPIRED","EXPORT_FAILED","ANNOTATION_NOT_FOUND","JOB_NOT_FOUND","JOB_ALREADY_RUNNING","JOB_PER_TENANT_CAP","JOB_TERMINAL","EMBEDDER_UNAVAILABLE","EMBEDDER_INVOCATION_FAILED","NAME_TAKEN","IDEMPOTENCY_REPLAY","CONCURRENT_MODIFICATION","RATE_LIMITED","RESOURCE_LIMIT_EXCEEDED","INTERNAL_ERROR","DEPENDENCY_UNAVAILABLE","MODEL_LOADING","MODEL_STARTING"],"description":"Stable error code enum. Pattern-match here, never on `message`. Adding values is non-breaking; renaming/removing is a major bump."},"ErrorResponse":{"type":"object","required":["error"],"properties":{"error":{"type":"object","required":["code","message"],"properties":{"code":{"$ref":"#/components/schemas/ErrorCode"},"message":{"type":"string","description":"Human-readable; free to evolve. Don't pattern-match on this."},"hint":{"type":"string","nullable":true,"description":"When present, a concrete next step (often references a CLI command)."},"details":{"type":"object","nullable":true,"description":"Per-`code` structured payload; schema documented per code."},"request_id":{"type":"string","nullable":true,"description":"Correlation id for support / log lookups."}}}}}},"securitySchemes":{"HTTPBearer":{"type":"http","description":"Cognito access token","scheme":"bearer"}}}}