{"openapi":"3.1.0","info":{"title":"midgard","summary":"Name + password identity service for small fly.io apps.","description":"**midgard** is a tiny name + password identity service.\n\nClients register, log in, and then attach `Authorization: Bearer <token>`\nto their requests. `/verify` checks the token without a DB hit, `/me`\nreturns the user record.\n\n- Passwords are stored as PBKDF2-SHA256 (200k iterations).\n- Tokens are HMAC-SHA256 signed and expire after 1 week.\n- Names: 2–40 chars, `[A-Za-z0-9_.-]`.\n- Passwords: ≥6 chars.\n- Recovery is mandatory: every account ships with a self-written\n  security question + answer, so accounts are recoverable even if\n  email is never attached.\n- Email is optional: when supplied, it adds Clerk's hosted email-reset\n  flow on top of the security question.\n- Ownership: a user may be a *dependent* of another user (its\n  `parent_uid`). The owning user implicitly has authority over its\n  descendants — this is consulted by external services (e.g.\n  switchboard) when authorizing cross-principal interactions.\n\nWhen `CLERK_SECRET_KEY` is set, users registering with an email are\ncreated in Clerk too; their password is verified via Clerk on login.\nUsers without an email stay fully local — no Clerk roundtrip. They\ncan attach an email later via `PUT /me/email`, which requires\nre-entering (or rotating) the password since Clerk needs the\nplaintext to take ownership of authentication.\n\nAdmin endpoints live under `/admin/*`, guarded by a shared\n`ADMIN_TOKEN` secret.\n","version":"0.14.1"},"paths":{"/admin/stats":{"get":{"tags":["admin"],"summary":"Return service-level counters.","description":"Lightweight summary for dashboards / health checks.","operationId":"stats_admin_stats_get","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"`Bearer <ADMIN_TOKEN>`. The ADMIN_TOKEN env var is a shared secret set via fly secrets.","title":"Authorization"},"description":"`Bearer <ADMIN_TOKEN>`. The ADMIN_TOKEN env var is a shared secret set via fly secrets."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Stats"}}}},"401":{"description":"Missing admin bearer token."},"403":{"description":"Wrong admin token."},"503":{"description":"Admin disabled (ADMIN_TOKEN env var not set)."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/admin/users":{"get":{"tags":["admin"],"summary":"List users, oldest first.","description":"Paginates with `limit` (1..1000) and `offset`. No filtering — for more, query the database directly.","operationId":"list_users_admin_users_get","parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","default":100,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","default":0,"title":"Offset"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"`Bearer <ADMIN_TOKEN>`. The ADMIN_TOKEN env var is a shared secret set via fly secrets.","title":"Authorization"},"description":"`Bearer <ADMIN_TOKEN>`. The ADMIN_TOKEN env var is a shared secret set via fly secrets."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserList"}}}},"401":{"description":"Missing admin bearer token."},"403":{"description":"Wrong admin token."},"503":{"description":"Admin disabled (ADMIN_TOKEN env var not set)."},"400":{"description":"Invalid pagination params."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/admin/users/{uid}":{"get":{"tags":["admin"],"summary":"Fetch one user by id.","operationId":"get_user_admin_users__uid__get","parameters":[{"name":"uid","in":"path","required":true,"schema":{"type":"integer","title":"Uid"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"`Bearer <ADMIN_TOKEN>`. The ADMIN_TOKEN env var is a shared secret set via fly secrets.","title":"Authorization"},"description":"`Bearer <ADMIN_TOKEN>`. The ADMIN_TOKEN env var is a shared secret set via fly secrets."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SingleUser"}}}},"401":{"description":"Missing admin bearer token."},"403":{"description":"Wrong admin token."},"503":{"description":"Admin disabled (ADMIN_TOKEN env var not set)."},"404":{"description":"User not found."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"tags":["admin"],"summary":"Permanently delete a user.","description":"Destructive. All tokens for this user immediately become useless on the next `/me` call (signature still validates until expiry, but the DB row is gone).","operationId":"delete_user_admin_users__uid__delete","parameters":[{"name":"uid","in":"path","required":true,"schema":{"type":"integer","title":"Uid"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"`Bearer <ADMIN_TOKEN>`. The ADMIN_TOKEN env var is a shared secret set via fly secrets.","title":"Authorization"},"description":"`Bearer <ADMIN_TOKEN>`. The ADMIN_TOKEN env var is a shared secret set via fly secrets."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeletedResponse"}}}},"401":{"description":"Missing admin bearer token."},"403":{"description":"Wrong admin token."},"503":{"description":"Admin disabled (ADMIN_TOKEN env var not set)."},"404":{"description":"User not found."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/admin/users/{uid}/password":{"post":{"tags":["admin"],"summary":"Set a new password for a user.","description":"The password is PBKDF2-hashed before storage; the plaintext never reaches disk.","operationId":"reset_password_admin_users__uid__password_post","parameters":[{"name":"uid","in":"path","required":true,"schema":{"type":"integer","title":"Uid"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"`Bearer <ADMIN_TOKEN>`. The ADMIN_TOKEN env var is a shared secret set via fly secrets.","title":"Authorization"},"description":"`Bearer <ADMIN_TOKEN>`. The ADMIN_TOKEN env var is a shared secret set via fly secrets."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PasswordReset"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ResetResponse"}}}},"401":{"description":"Missing admin bearer token."},"403":{"description":"Wrong admin token."},"503":{"description":"Admin disabled (ADMIN_TOKEN env var not set)."},"404":{"description":"User not found."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/admin/origins":{"get":{"tags":["admin"],"summary":"List allowed CORS origins.","description":"Origins are DB rows managed through this endpoint.","operationId":"list_origins_admin_origins_get","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"`Bearer <ADMIN_TOKEN>`. The ADMIN_TOKEN env var is a shared secret set via fly secrets.","title":"Authorization"},"description":"`Bearer <ADMIN_TOKEN>`. The ADMIN_TOKEN env var is a shared secret set via fly secrets."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OriginList"}}}},"401":{"description":"Missing admin bearer token."},"403":{"description":"Wrong admin token."},"503":{"description":"Admin disabled (ADMIN_TOKEN env var not set)."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"post":{"tags":["admin"],"summary":"Allow a new CORS origin.","description":"Persists the origin to the DB so it survives restarts. Before accepting, sends a HEAD request to the origin's root to confirm something's actually there — catches typos before they silently poison the allow-list. Takes effect immediately: the next preflight from that origin will succeed.","operationId":"add_origin_admin_origins_post","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"`Bearer <ADMIN_TOKEN>`. The ADMIN_TOKEN env var is a shared secret set via fly secrets.","title":"Authorization"},"description":"`Bearer <ADMIN_TOKEN>`. The ADMIN_TOKEN env var is a shared secret set via fly secrets."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/OriginIn"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"401":{"description":"Missing admin bearer token."},"403":{"description":"Wrong admin token."},"503":{"description":"Admin disabled (ADMIN_TOKEN env var not set)."},"400":{"description":"Origin format is invalid or host is unreachable."},"409":{"description":"Origin already allowed."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"tags":["admin"],"summary":"Remove an allowed origin.","description":"Takes the origin as a query param so the value doesn't have to be URL-path-encoded.","operationId":"remove_origin_admin_origins_delete","parameters":[{"name":"origin","in":"query","required":true,"schema":{"type":"string","title":"Origin"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"`Bearer <ADMIN_TOKEN>`. The ADMIN_TOKEN env var is a shared secret set via fly secrets.","title":"Authorization"},"description":"`Bearer <ADMIN_TOKEN>`. The ADMIN_TOKEN env var is a shared secret set via fly secrets."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"401":{"description":"Missing admin bearer token."},"403":{"description":"Wrong admin token."},"503":{"description":"Admin disabled (ADMIN_TOKEN env var not set)."},"404":{"description":"Origin not in the allow-list."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/health":{"get":{"tags":["meta"],"summary":"Health","operationId":"health_health_get","responses":{"200":{"description":"Successful Response","content":{"text/plain":{"schema":{"type":"string"}}}}}}},"/register":{"post":{"tags":["auth"],"summary":"Create a new account.","description":"Creates a user with the given name and password and returns a bearer token plus the new user record.\n\nA `recovery_question` + `recovery_answer` pair is **required**: it's stored locally so the user can always recover without depending on email (see `/auth/forgot-password`).\n\nIf `email` is also supplied **and** `CLERK_SECRET_KEY` is configured, the user is created in Clerk too; their `clerk_user_id` is stored on the midgard row and Clerk becomes the password authority for that user. Email recovery then layers on top of the security question.\n\nReturns **409** on a name/email collision, **400** on bad format, **502** if the upstream Clerk call fails.","operationId":"register_register_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RegisterRequest"}}},"required":true},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TokenResponse"}}}},"400":{"description":"Bad Request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDetail"}}}},"409":{"description":"Conflict","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDetail"}}}},"502":{"description":"Clerk call failed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDetail"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/login":{"post":{"tags":["auth"],"summary":"Exchange credentials for a bearer token.","description":"For users with a `clerk_user_id` on file, the password is verified against Clerk; otherwise it's verified locally (PBKDF2). The same **401** is returned for wrong password and unknown user — names are not enumerable through this endpoint.","operationId":"login_login_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Credentials"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TokenResponse"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDetail"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/auth/forgot-password":{"post":{"tags":["recovery"],"summary":"Start password recovery for a user.","description":"Returns one of:\n\n- `{\"reset_url\": \"…\"}` — user has Clerk on file; client redirects them there to receive an email reset link.\n- `{\"question\": \"…\"}` — user has a local recovery question; client asks them to answer it, then posts to `/auth/reset-password`.\n- **404** — user is unknown or has no recovery on file.\n\n(Future revision will mask the 404 to prevent name enumeration.)","operationId":"forgot_password_auth_forgot_password_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ForgotPasswordRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ForgotPasswordResponse"}}}},"404":{"description":"Not Found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDetail"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/auth/reset-password":{"post":{"tags":["recovery"],"summary":"Reset a password by answering the recovery question (local path only).","description":"For users with a Clerk `clerk_user_id`, recovery happens through Clerk's hosted reset page (see `/auth/forgot-password`); this endpoint always returns **401** for them.\n\nVerifies the recovery answer (case/whitespace-insensitive), updates the local password, and returns a fresh token. A single **401** covers unknown user, missing recovery, and wrong answer.","operationId":"reset_password_auth_reset_password_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ResetPasswordRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TokenResponse"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDetail"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/verify":{"get":{"tags":["session"],"summary":"Verify","operationId":"verify_verify_get","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/VerifyResponse"}}}},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDetail"}}},"description":"Unauthorized"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/me":{"get":{"tags":["session"],"summary":"Me","operationId":"me_me_get","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MeResponse"}}}},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDetail"}}},"description":"Unauthorized"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDetail"}}},"description":"Not Found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/me/recovery":{"put":{"tags":["session"],"summary":"Set Recovery","operationId":"set_recovery_me_recovery_put","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RecoverySetRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OkResponse"}}}},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDetail"}}},"description":"Unauthorized"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"tags":["session"],"summary":"Clear Recovery","operationId":"clear_recovery_me_recovery_delete","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OkResponse"}}}},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDetail"}}},"description":"Unauthorized"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/me/email":{"put":{"tags":["session"],"summary":"Attach an email to an existing account.","description":"For users who registered without an email and now want one on file (for service updates, recovery, etc.).\n\nThe caller must re-enter their current `password`. If Clerk is configured, midgard also creates a Clerk user and stores the `clerk_user_id` so the user gets the hosted email-reset flow; otherwise the email is just saved locally and Clerk can be wired up later. Optionally pass `new_password` to rotate at the same time — the new value becomes the password locally and in Clerk (if applicable).\n\nReturns **400** on bad email format, **401** on wrong current password, **409** if the email is already taken or the account already has an email/Clerk identity, **502** if Clerk rejects the request.","operationId":"attach_email_me_email_put","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AttachEmailRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OkResponse"}}}},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDetail"}}},"description":"Bad Request"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDetail"}}},"description":"Unauthorized"},"409":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDetail"}}},"description":"Conflict"},"502":{"description":"Clerk call failed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDetail"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/me/dependents":{"post":{"tags":["session"],"summary":"Create a dependent user owned by the caller.","description":"Creates a new midgard user whose `parent_uid` is the caller. Used for agents or sub-accounts the caller is responsible for. Dependents authenticate normally via `/login` with their own password; ownership is consulted by external services (e.g. switchboard) when authorizing cross-principal interactions.\n\nEmail/Clerk integration is not exposed here — dependents are expected to be agents managed by the parent. Attach an email later via `/me/email` if needed.\n\nA `recovery_question` + `recovery_answer` pair is **optional** here (unlike `/register`): the owner is itself the dependent's non-email recovery path via `/me/dependents/{uid}/password`, and the holder can self-author a question later via `PUT /me/recovery`.","operationId":"create_dependent_me_dependents_post","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DependentCreateRequest"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TokenResponse"}}}},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDetail"}}},"description":"Bad Request"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDetail"}}},"description":"Unauthorized"},"409":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDetail"}}},"description":"Conflict"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"get":{"tags":["session"],"summary":"List the caller's immediate dependents.","description":"Returns the immediate children of the caller — users whose `parent_uid` is the caller's uid. One level only; descendants of dependents are not flattened in.","operationId":"list_my_dependents_me_dependents_get","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DependentsListResponse"}}}},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDetail"}}},"description":"Unauthorized"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/me/dependents/{uid}/password":{"post":{"tags":["session"],"summary":"Reissue the password for a dependent the caller owns.","description":"Sets a new password for `uid` and returns a fresh token for that dependent. Authority follows the ownership graph: the caller must appear in the target's ancestor chain (immediate parent or any ancestor above it), mirroring the rule external services use (see `/users/{uid}/ancestors`).\n\nThis is a dependent's primary non-email recovery path, which is why a recovery question is optional at creation.\n\nReturns **403** if the caller does not own the target, **404** if the target is unknown, and **409** if the dependent's password is managed by Clerk (reset goes through Clerk's hosted flow instead).","operationId":"reissue_dependent_password_me_dependents__uid__password_post","parameters":[{"name":"uid","in":"path","required":true,"schema":{"type":"integer","title":"Uid"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DependentPasswordResetRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TokenResponse"}}}},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDetail"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDetail"}}},"description":"Forbidden"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDetail"}}},"description":"Not Found"},"409":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDetail"}}},"description":"Conflict"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/users/{uid}/ancestors":{"get":{"tags":["session"],"summary":"Look up the parentage chain for a user.","description":"Returns the ancestor chain for `uid` — immediate parent first, walking up to the root. Empty for root users.\n\nUsed by external services (e.g. switchboard) to decide whether the caller has implicit authority over a target principal: if the caller's uid appears in the target's ancestor chain, ownership grants them authority. Authentication is required but the response is not scoped to the caller — anyone with a valid token can read parentage for any user.","operationId":"ancestors_users__uid__ancestors_get","parameters":[{"name":"uid","in":"path","required":true,"schema":{"type":"integer","title":"Uid"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AncestorsResponse"}}}},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDetail"}}},"description":"Unauthorized"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorDetail"}}},"description":"Not Found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}}},"components":{"schemas":{"AdminUser":{"properties":{"id":{"type":"integer","title":"Id"},"name":{"type":"string","title":"Name"},"created_at":{"type":"string","title":"Created At","description":"ISO-8601 creation timestamp."}},"type":"object","required":["id","name","created_at"],"title":"AdminUser"},"Ancestor":{"properties":{"id":{"type":"integer","title":"Id"},"name":{"type":"string","title":"Name"}},"type":"object","required":["id","name"],"title":"Ancestor"},"AncestorsResponse":{"properties":{"ancestors":{"items":{"$ref":"#/components/schemas/Ancestor"},"type":"array","title":"Ancestors","description":"Parentage chain, immediate parent first, walking up to the root. Empty when the user is a root account."}},"type":"object","required":["ancestors"],"title":"AncestorsResponse"},"AttachEmailRequest":{"properties":{"email":{"type":"string","maxLength":200,"title":"Email","examples":["alice@example.com"]},"password":{"type":"string","maxLength":200,"minLength":6,"title":"Password","description":"The user's current password — re-entered to authorize the change. Becomes the password Clerk takes ownership of (unless `new_password` is supplied)."},"new_password":{"anyOf":[{"type":"string","maxLength":200,"minLength":6},{"type":"null"}],"title":"New Password","description":"Optional rotation: if supplied, replaces the existing password locally and is registered with Clerk."}},"type":"object","required":["email","password"],"title":"AttachEmailRequest"},"Credentials":{"properties":{"name":{"type":"string","maxLength":40,"minLength":2,"title":"Name","description":"Unique user name. 2–40 chars, [A-Za-z0-9_.-].","examples":["alice"]},"password":{"type":"string","maxLength":200,"minLength":6,"title":"Password","description":"Plain password.","examples":["hunter2-correct-horse"]}},"type":"object","required":["name","password"],"title":"Credentials"},"DeletedResponse":{"properties":{"deleted":{"type":"integer","title":"Deleted","description":"Id of the deleted user."}},"type":"object","required":["deleted"],"title":"DeletedResponse"},"DependentCreateRequest":{"properties":{"name":{"type":"string","maxLength":40,"minLength":2,"title":"Name","description":"Unique user name for the dependent.","examples":["alice-bot"]},"password":{"type":"string","maxLength":200,"minLength":6,"title":"Password","description":"Password for the dependent. The creator should hold a copy if they intend to log in as this user."},"recovery_question":{"anyOf":[{"type":"string","maxLength":200,"minLength":3},{"type":"null"}],"title":"Recovery Question","description":"Optional recovery question. Unlike root accounts, a dependent's non-email recovery path is its owner, who can reissue the password via `/me/dependents/{uid}/password`. The holder (agent or human) can also self-author a question later via `PUT /me/recovery`. Supply this only if you want a question set at creation; if given, `recovery_answer` is required too.","examples":["Creator's first pet?"]},"recovery_answer":{"anyOf":[{"type":"string","maxLength":200,"minLength":1},{"type":"null"}],"title":"Recovery Answer","description":"Answer to `recovery_question`. Required iff `recovery_question` is supplied.","examples":["Mittens"]}},"type":"object","required":["name","password"],"title":"DependentCreateRequest"},"DependentPasswordResetRequest":{"properties":{"new_password":{"type":"string","maxLength":200,"minLength":6,"title":"New Password","description":"New password for the owned dependent.","examples":["fresh-strong-pw"]}},"type":"object","required":["new_password"],"title":"DependentPasswordResetRequest"},"DependentSummary":{"properties":{"id":{"type":"integer","title":"Id"},"name":{"type":"string","title":"Name"},"created_at":{"type":"string","title":"Created At","description":"ISO-8601 creation timestamp."}},"type":"object","required":["id","name","created_at"],"title":"DependentSummary"},"DependentsListResponse":{"properties":{"dependents":{"items":{"$ref":"#/components/schemas/DependentSummary"},"type":"array","title":"Dependents"}},"type":"object","required":["dependents"],"title":"DependentsListResponse"},"ErrorDetail":{"properties":{"detail":{"type":"string","title":"Detail","examples":["invalid credentials"]}},"type":"object","required":["detail"],"title":"ErrorDetail"},"ForgotPasswordRequest":{"properties":{"name":{"type":"string","maxLength":40,"minLength":2,"title":"Name","examples":["alice"]}},"type":"object","required":["name"],"title":"ForgotPasswordRequest"},"ForgotPasswordResponse":{"properties":{"question":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Question","description":"The user's self-written recovery question (local path)."},"reset_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Reset Url","description":"Clerk hosted reset URL (email path). Mutually exclusive with question."}},"type":"object","title":"ForgotPasswordResponse"},"HTTPValidationError":{"properties":{"detail":{"items":{"$ref":"#/components/schemas/ValidationError"},"type":"array","title":"Detail"}},"type":"object","title":"HTTPValidationError"},"MeResponse":{"properties":{"user":{"$ref":"#/components/schemas/UserFull"}},"type":"object","required":["user"],"title":"MeResponse"},"OkResponse":{"properties":{"ok":{"type":"boolean","title":"Ok","default":true}},"type":"object","title":"OkResponse"},"Origin":{"properties":{"id":{"type":"integer","title":"Id"},"value":{"type":"string","title":"Value"},"created_at":{"type":"string","title":"Created At"}},"type":"object","required":["id","value","created_at"],"title":"Origin"},"OriginIn":{"properties":{"origin":{"type":"string","maxLength":200,"title":"Origin","description":"Full origin URL: scheme + host (+ optional port). No trailing slash, no path.","examples":["https://agora.fly.dev"]}},"type":"object","required":["origin"],"title":"OriginIn"},"OriginList":{"properties":{"origins":{"items":{"$ref":"#/components/schemas/Origin"},"type":"array","title":"Origins"},"count":{"type":"integer","title":"Count"}},"type":"object","required":["origins","count"],"title":"OriginList"},"PasswordReset":{"properties":{"password":{"type":"string","maxLength":200,"minLength":6,"title":"Password","description":"New password to set on the user.","examples":["new-strong-password"]}},"type":"object","required":["password"],"title":"PasswordReset"},"RecoverySetRequest":{"properties":{"question":{"type":"string","maxLength":200,"minLength":3,"title":"Question","examples":["Name of my first pet?"]},"answer":{"type":"string","maxLength":200,"minLength":1,"title":"Answer","examples":["Mittens"]}},"type":"object","required":["question","answer"],"title":"RecoverySetRequest"},"RegisterRequest":{"properties":{"name":{"type":"string","maxLength":40,"minLength":2,"title":"Name","description":"Unique user name. 2–40 chars, [A-Za-z0-9_.-].","examples":["alice"]},"password":{"type":"string","maxLength":200,"minLength":6,"title":"Password","description":"Plain password.","examples":["hunter2-correct-horse"]},"recovery_question":{"type":"string","maxLength":200,"minLength":3,"title":"Recovery Question","description":"Self-written recovery question. Required so every account has a non-email recovery path.","examples":["Name of my first pet?"]},"recovery_answer":{"type":"string","maxLength":200,"minLength":1,"title":"Recovery Answer","description":"Answer to recovery_question. Stored hashed; case/whitespace-insensitive.","examples":["Mittens"]},"email":{"anyOf":[{"type":"string","maxLength":200},{"type":"null"}],"title":"Email","description":"Optional email. Triggers Clerk-mediated registration when set, layering email-based recovery on top of the required security question.","examples":["alice@example.com"]}},"type":"object","required":["name","password","recovery_question","recovery_answer"],"title":"RegisterRequest"},"ResetPasswordRequest":{"properties":{"name":{"type":"string","maxLength":40,"minLength":2,"title":"Name","examples":["alice"]},"answer":{"type":"string","maxLength":200,"minLength":1,"title":"Answer","description":"Answer to the recovery question (case/whitespace-insensitive)."},"new_password":{"type":"string","maxLength":200,"minLength":6,"title":"New Password"}},"type":"object","required":["name","answer","new_password"],"title":"ResetPasswordRequest"},"ResetResponse":{"properties":{"reset":{"type":"integer","title":"Reset","description":"Id of the user whose password was reset."}},"type":"object","required":["reset"],"title":"ResetResponse"},"SingleUser":{"properties":{"user":{"$ref":"#/components/schemas/AdminUser"}},"type":"object","required":["user"],"title":"SingleUser"},"Stats":{"properties":{"users":{"type":"integer","title":"Users","description":"Total registered users."}},"type":"object","required":["users"],"title":"Stats"},"TokenResponse":{"properties":{"token":{"type":"string","title":"Token","description":"HMAC-signed bearer token (1-week TTL)."},"user":{"$ref":"#/components/schemas/UserPublic"}},"type":"object","required":["token","user"],"title":"TokenResponse"},"UserFull":{"properties":{"id":{"type":"integer","title":"Id","description":"Stable integer user id.","examples":[1]},"name":{"type":"string","title":"Name","description":"User's display name.","examples":["alice"]},"created_at":{"type":"string","title":"Created At","description":"ISO-8601 creation timestamp."},"has_recovery_question":{"type":"boolean","title":"Has Recovery Question","default":false},"has_email":{"type":"boolean","title":"Has Email","default":false},"parent_uid":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Parent Uid","description":"Owning principal's uid, or null for a root user."}},"type":"object","required":["id","name","created_at"],"title":"UserFull"},"UserList":{"properties":{"users":{"items":{"$ref":"#/components/schemas/AdminUser"},"type":"array","title":"Users"},"limit":{"type":"integer","title":"Limit"},"offset":{"type":"integer","title":"Offset"}},"type":"object","required":["users","limit","offset"],"title":"UserList"},"UserPublic":{"properties":{"id":{"type":"integer","title":"Id","description":"Stable integer user id.","examples":[1]},"name":{"type":"string","title":"Name","description":"User's display name.","examples":["alice"]}},"type":"object","required":["id","name"],"title":"UserPublic"},"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"},"VerifyResponse":{"properties":{"user":{"$ref":"#/components/schemas/UserPublic"},"exp":{"type":"integer","title":"Exp","examples":[1769817600]}},"type":"object","required":["user","exp"],"title":"VerifyResponse"}}},"tags":[{"name":"auth","description":"Public sign-up / sign-in flow."},{"name":"recovery","description":"Forgotten-password recovery."},{"name":"session","description":"Token-protected introspection."},{"name":"admin","description":"Shared-secret admin tasks."},{"name":"meta","description":"Health and landing page."},{"name":"screens","description":"HTML sign-in/sign-up pages."}]}