{"openapi":"3.1.0","info":{"title":"VisaForma API","description":"The VisaForma public API: the backend of [visaforma.com](https://visaforma.com), a\nplatform that turns a visa application into a guided, market-specific questionnaire\n(a *flow*), collects the documents, and hands the case to an operator.\n\n## Base URL\n\nAll paths in this document are relative to **`https://visaforma.com/api`** (see `servers`). The\nsame API answers the web app and third-party integrations; there is no separate\npartner host.\n\n## Authentication\n\n* **Public read surface — no credentials.** Everything under `/public/*`, the\n  `/markets/{market}/directions*` catalog and flow endpoints, `/promotions/current-offer`,\n  `/support/public/*`, `/healthz` / `/readyz`, and the embed hand-off are callable\n  without a session. They are cacheable (`Cache-Control` / `ETag` where stated) and\n  fenced by a generous per-IP rate limit (see below).\n* **Self-serve API key — optional, bigger budget.** `POST /developers/api-keys`\n  with `{\"email\": \"...\", \"label\": \"...\"}` returns a `vf_live_…` key at once (no\n  confirmation, no approval; shown once, only its hash is stored). Send it as\n  `Authorization: Bearer vf_live_…` on the public read endpoints (security scheme\n  `ApiKeyBearer`) and the quota is charged per key (`public_read_key`,\n  3000/min) instead of per IP (300/min). An unknown or revoked key is\n  `401 {\"detail\": \"invalid api key\"}` — never a silent fall-back to the IP budget.\n  `GET` / `DELETE /developers/api-keys/current` inspect and revoke the presented key.\n* **Private surface — cookie session.** Applicant endpoints (`/applications`, `/me`,\n  `/documents`, `/billing`, …) require the httpOnly session cookie issued by\n  `/auth/google/*` or `/auth/login-link/*`; staff endpoints (`/operator/*`,\n  `/admin/*`, `/staff/*`, config studio) require a staff session from\n  `/auth/staff/*` with the role named in each operation's description. Requests are\n  scoped to the caller: an applicant sees only their own resources, staff only the\n  markets / directions they are granted.\n\nEach operation's description states its audience (`public`, `applicant`, `staff`\nwith the minimum role) and what it returns.\n\n## Versioning and stability\n\nThe API is **date-versioned**, not path-versioned: every response carries\n`API-Version: YYYY-MM-DD` (currently `2026-08-21`). The documented surface is\nstable — changes are additive (new fields, new operations, new enum members that\nclients must tolerate). A **breaking** change is never silent: the affected operation\nis marked `deprecated` in this document and starts answering with `Deprecation`\n(RFC 9745) and `Sunset` (RFC 8594) headers, plus a `Link: <…>; rel=\"successor-version\"`\nto its replacement, **at least 90 days** before the sunset date. No operation is\ndeprecated today.\n\n## Rate limits\n\nRate-limited responses — successful ones included — carry the IETF\n`RateLimit-Policy` / `RateLimit` headers (draft-ietf-httpapi-ratelimit-headers), e.g.\n`RateLimit-Policy: \"public_read_ip\";q=300;w=60` and\n`RateLimit: \"public_read_ip\";r=287;t=41`.\nA refusal is `429 Too Many Requests` with `Retry-After` (seconds). The zero-auth read\nsurface shares one per-IP budget (`public_read_ip`, 300/min, the free tier) or,\nwith a key, one per-key budget (`public_read_key`, 3000/min); write endpoints\nhave their own, tighter budgets named in their descriptions.\n\n## Errors\n\nErrors are JSON `{\"detail\": ...}` with a conventional status: `401` (no session),\n`403` (wrong audience / not yours), `404`, `409` (state conflict), `412` / `428`\n(optimistic concurrency via `If-Match` / `ETag`), `422` (validation), `429`, `503`\n(a dependency is unavailable; safe to retry).\n\nMore at [https://visaforma.com/developers](https://visaforma.com/developers).\n","contact":{"name":"VisaForma developer support","url":"https://visaforma.com/developers","email":"account@visaforma.com"},"version":"0.1.0"},"servers":[{"url":"https://visaforma.com/api","description":"Production"}],"paths":{"/healthz":{"get":{"tags":["health"],"summary":"Liveness probe","description":"Answers `status: ok` whenever the process serves HTTP. No authentication required. Not rate-limited; excluded from access logs.","operationId":"healthz_healthz_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":{"type":"string"},"type":"object","title":"Response Healthz Healthz Get"}}}}}}},"/readyz":{"get":{"tags":["health"],"summary":"Readiness probe","description":"Readiness plus the state of the shared rate-limit store: `status` is `ready`, or `degraded` with `rate_limit_backend` naming the cause (always 200, so a store blink never pulls every instance out of rotation); also reports the build `version`. No authentication required. Not rate-limited; excluded from access logs.","operationId":"readyz_readyz_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":{"type":"string"},"type":"object","title":"Response Readyz Readyz Get"}}}}}}},"/admin/staff/invites":{"post":{"tags":["admin"],"summary":"Invite a new staff member (returns a one-time activation token)","description":"Provisions a staff identity with its market/role grant and returns a one-time activation token to hand to the invitee out of band; audit-logged. Requires a staff session with role `admin` or higher.","operationId":"admin_invite_staff_admin_staff_invites_post","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/StaffInviteCreate"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StaffInviteIssued"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"get":{"tags":["admin"],"summary":"List staff invites with their lifecycle status","description":"Lists issued staff invites with their state (pending, spent, revoked, expired). Requires a staff session with role `admin` or higher.","operationId":"admin_list_staff_invites_admin_staff_invites_get","parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":200,"minimum":1,"default":50,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0,"title":"Offset"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StaffInviteList"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/admin/staff/invites/{invite_id}/revoke":{"post":{"tags":["admin"],"summary":"Revoke an unused staff invite","description":"Withdraws an invite that has not been activated; an already-spent invite is refused. Audit-logged. Requires a staff session with role `admin` or higher.","operationId":"admin_revoke_staff_invite_admin_staff_invites__invite_id__revoke_post","parameters":[{"name":"invite_id","in":"path","required":true,"schema":{"type":"string","title":"Invite Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StaffInviteView"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/admin/staff":{"get":{"tags":["admin"],"summary":"List staff with their duty schedule and computed on-duty flag","description":"Страница ростера операторов, по возрастанию email.\n\nRead-only: ни строки не пишется и аудит-записи не возникает — читать список\nсотрудников не привилегия, которую надо журналировать, в отличие от выдачи\nгранта.\n\nДежурность считается сервером на момент запроса (текущий момент, приведённый\nк московской зоне): эндпоинт намеренно не принимает ни времени, ни даты, ни\nзоны — иначе «сейчас» стало бы утверждением клиента, и ростер разошёлся бы\nдля тех, кого реально пингует бот. Признак отражает ТОЛЬКО расписание, поэтому\nнеактивный или непривязанный сотрудник в свою смену тоже дежурит.\n\nПоверхность не привязана к рынку: авторитет — только ролевая ступень\nадминистратора, как и для прочих кадровых роутов админки.","operationId":"admin_list_staff_admin_staff_get","parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":200,"minimum":1,"default":50,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0,"title":"Offset"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StaffList"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/admin/staff/{staff_id}/working-hours":{"put":{"tags":["admin"],"summary":"Replace an operator's weekly working hours (Europe/Moscow)","description":"Заменить расписание оператора целиком; ответ — итог, по возрастанию дня.\n\nПоверхность не market-scoped намеренно: авторитет — только ролевая\nступень ``min_role=\"admin\"``, рыночный/направленческий грант админа при\nрешении доступа не участвует (расписание не принадлежит рынку).\nРоль и ``active`` ЦЕЛИ тоже ничего не решают: 404 определяется ровно\nотсутствием строки ``staff``.\n\nМутация и аудит — одна транзакция (arch §10). Дифф PII-free: только\n``weekday`` + ``\"HH:MM\"``, ни email, ни имени, ни ``tg_user_id``.","operationId":"admin_put_staff_working_hours_admin_staff__staff_id__working_hours_put","parameters":[{"name":"staff_id","in":"path","required":true,"schema":{"type":"string","title":"Staff Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WorkingHoursPut"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StaffWorkingHoursView"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/admin/staff/activation/start":{"post":{"tags":["admin"],"summary":"Begin activation: enrol TOTP and return the otpauth:// URI for the QR","description":"First activation step for an invited staff member: validates the one-time activation token, enrols a TOTP secret and returns the `otpauth://` URI to render as a QR code. Public: the signed invite token is the only credential.","operationId":"admin_activation_start_admin_staff_activation_start_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/StaffActivationStart"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StaffActivationChallenge"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/admin/staff/activation/complete":{"post":{"tags":["admin"],"summary":"Finish activation: set the password, confirm the first TOTP code, sign in","description":"Second activation step: sets the password, confirms the first TOTP code, burns the invite and sets the httpOnly staff session cookie. Public: the signed invite token is the only credential.","operationId":"admin_activation_complete_admin_staff_activation_complete_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/StaffActivationComplete"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StaffSessionResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/analytics/events":{"post":{"tags":["analytics"],"summary":"Ingest a client funnel event","description":"Accepts one browser funnel event and answers 202 when it is a registered client event with a valid props schema and no PII; anything else (unknown name, server-authoritative event, schema mismatch, PII) is refused with 422. Write-only: there is no read-back. No authentication required.","operationId":"analytics_ingest_analytics_events_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AnalyticsEventIn"}}},"required":true},"responses":{"202":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/applications":{"get":{"tags":["applications"],"summary":"List the current user's applications (ЛК)","description":"List the caller's own applications with their client-facing status. Requires an applicant session; owner-only.","operationId":"applications_list_applications_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApplicationListResponse"}}}}}},"post":{"tags":["applications"],"summary":"Create an application (idempotent by client_request_id)","description":"Create a draft application for a market and direction. Idempotent on `client_request_id`: repeating a request returns the same case. Rate-limited per actor (`application_create`, 20/60s) and per IP (`application_create_ip`, 100/60s); a refusal is 429 with `Retry-After`. Requires an applicant session; owner-only.","operationId":"applications_create_applications_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateApplicationRequest"}}},"required":true},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApplicationDetail"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/applications/{application_id}":{"get":{"tags":["applications"],"summary":"Get an application (client-projected status)","description":"One application with its client-projected status, answers and documents. The response carries an `ETag` to send back as `If-Match` on stage saves. Requires an applicant session; owner-only.","operationId":"applications_get_applications__application_id__get","parameters":[{"name":"application_id","in":"path","required":true,"schema":{"type":"string","title":"Application Id"}},{"name":"locale","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Locale"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApplicationDetail"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"tags":["applications"],"summary":"Delete a DRAFT application","description":"Hard-delete an application that is still a draft; any later state is 409. Requires an applicant session; owner-only.","operationId":"applications_delete_applications__application_id__delete","parameters":[{"name":"application_id","in":"path","required":true,"schema":{"type":"string","title":"Application Id"}}],"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/applications/{application_id}/stages/{stage_key}":{"post":{"tags":["applications"],"summary":"Autosave one stage (allowed in DRAFT and NEEDS_INFO)","description":"Save the answers of one questionnaire stage (allowed in DRAFT and NEEDS_INFO). Send the last `ETag` as `If-Match`: a stale revision is 409 with the current `ETag`; other states are 409. Requires an applicant session; owner-only.","operationId":"applications_save_stage_applications__application_id__stages__stage_key__post","parameters":[{"name":"application_id","in":"path","required":true,"schema":{"type":"string","title":"Application Id"}},{"name":"stage_key","in":"path","required":true,"schema":{"type":"string","title":"Stage Key"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SaveStageRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApplicationDetail"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/applications/{application_id}/sensitive/{field_key}":{"put":{"tags":["applications"],"summary":"Write one sensitive field to the encrypted store (returns its mask)","description":"Write one sensitive value (e.g. a passport number) to the encrypted store. The plaintext is never returned: the response is the field's mask (last 4 characters only). Requires an applicant session; owner-only.","operationId":"applications_write_sensitive_applications__application_id__sensitive__field_key__put","parameters":[{"name":"application_id","in":"path","required":true,"schema":{"type":"string","title":"Application Id"}},{"name":"field_key","in":"path","required":true,"schema":{"type":"string","title":"Field Key"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SensitiveWriteRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SensitiveFieldMask"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/applications/{application_id}/submit":{"post":{"tags":["applications"],"summary":"Submit (full revalidation by pin; payment guard downstream)","description":"Submit a draft for processing after full revalidation against the pinned flow; validation failures and wrong states are 409. Payment is enforced downstream of the submit. Requires an applicant session; owner-only.","operationId":"applications_submit_applications__application_id__submit_post","parameters":[{"name":"application_id","in":"path","required":true,"schema":{"type":"string","title":"Application Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SubmitRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SubmitResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/applications/{application_id}/resubmit":{"post":{"tags":["applications"],"summary":"Applicant 'Send to review' from NEEDS_INFO (resolves InfoRequest)","description":"Send a NEEDS_INFO application back to review, resolving the open info request. Wrong state is 409. Requires an applicant session; owner-only.","operationId":"applications_resubmit_applications__application_id__resubmit_post","parameters":[{"name":"application_id","in":"path","required":true,"schema":{"type":"string","title":"Application Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApplicationDetail"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/applications/{application_id}/info-requests/{info_request_id}/response":{"post":{"tags":["applications"],"summary":"Applicant answers an open InfoRequest (free text + attachments)","description":"Answer an open operator info-request with free text and optional attachments; a request that is no longer open is 409. Requires an applicant session; owner-only.","operationId":"applications_answer_info_request_applications__application_id__info_requests__info_request_id__response_post","parameters":[{"name":"application_id","in":"path","required":true,"schema":{"type":"string","title":"Application Id"}},{"name":"info_request_id","in":"path","required":true,"schema":{"type":"string","title":"Info Request Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/InfoRequestResponseCreate"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InfoRequestResponseView"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/operator/applications":{"get":{"tags":["operator"],"summary":"List/queue applications with filters (scoped market ∩ direction)","description":"The operator queue: applications filtered by status, assignee and search, limited to the caller's market and direction grants. Requires a staff session with role `operator` or higher; market-scoped.","operationId":"operator_list_operator_applications_get","parameters":[{"name":"status","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Status"}},{"name":"market","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Market"}},{"name":"assigned_to","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Assigned To"}},{"name":"unassigned","in":"query","required":false,"schema":{"type":"boolean","description":"queue: only unowned cases","default":false,"title":"Unassigned"},"description":"queue: only unowned cases"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OperatorApplicationList"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/operator/applications/{application_id}":{"get":{"tags":["operator"],"summary":"Operator case card (pinned render, applicants, docs, checklists)","description":"The full case card: pinned flow render, applicants, documents, checklists and deliverables. Requires a staff session with role `operator` or higher; market-scoped.","operationId":"operator_get_operator_applications__application_id__get","parameters":[{"name":"application_id","in":"path","required":true,"schema":{"type":"string","title":"Application Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OperatorApplicationDetail"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/operator/applications/{application_id}/claim":{"post":{"tags":["operator"],"summary":"Claim the case (atomic CAS on applications.assigned_to)","description":"Assign the case to the caller with an atomic compare-and-set; a case already owned by another operator is 409. Requires a staff session with role `operator` or higher; market-scoped.","operationId":"operator_claim_operator_applications__application_id__claim_post","parameters":[{"name":"application_id","in":"path","required":true,"schema":{"type":"string","title":"Application Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ClaimResult"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/operator/applications/{application_id}/transition":{"post":{"tags":["operator"],"summary":"Drive an FSM transition (transition(application, event, actor))","description":"Apply one FSM event to the case (approve, reject, request info, …); an event illegal in the current state is 409. Returns the updated case card. Requires a staff session with role `operator` or higher; market-scoped.","operationId":"operator_transition_operator_applications__application_id__transition_post","parameters":[{"name":"application_id","in":"path","required":true,"schema":{"type":"string","title":"Application Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TransitionRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OperatorApplicationDetail"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/operator/applications/{application_id}/info-requests":{"post":{"tags":["operator"],"summary":"Open an InfoRequest (→ NEEDS_INFO)","description":"Open an info-request that moves the case to NEEDS_INFO and notifies the applicant; wrong state is 409. Requires a staff session with role `operator` or higher; market-scoped.","operationId":"operator_create_info_request_operator_applications__application_id__info_requests_post","parameters":[{"name":"application_id","in":"path","required":true,"schema":{"type":"string","title":"Application Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/InfoRequestCreate"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InfoRequestView"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/operator/applications/{application_id}/checklist":{"post":{"tags":["operator"],"summary":"Toggle a per-applicant checklist item","description":"Toggle one per-applicant checklist item and return the updated case card. Requires a staff session with role `operator` or higher; market-scoped.","operationId":"operator_update_checklist_operator_applications__application_id__checklist_post","parameters":[{"name":"application_id","in":"path","required":true,"schema":{"type":"string","title":"Application Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ChecklistUpdate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OperatorApplicationDetail"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/operator/applications/{application_id}/applicants/{applicant_id}/external-artifacts":{"patch":{"tags":["operator"],"summary":"Set an applicant's external submission artefacts","description":"Persist the operator-entered external submission artefacts (slice A9).\n\nThe CRM saves the free-string references the operator reads off the government\nportal — ``external_application_ref`` next to the FILING checklist and\n``visa_grant_number`` from the F14/F12b visa-result widget. Only fields present\nin the request body are written, so the two inputs can be saved independently\nat their own points in the case lifecycle; an absent field is left untouched.\nSame fine market ∩ direction scope gate as the case card (V10): an out-of-scope\nactor gets a 404, existence is not disclosed.","operationId":"operator_set_external_artifacts_operator_applications__application_id__applicants__applicant_id__external_artifacts_patch","parameters":[{"name":"application_id","in":"path","required":true,"schema":{"type":"string","title":"Application Id"}},{"name":"applicant_id","in":"path","required":true,"schema":{"type":"string","title":"Applicant Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/OperatorApplicantPatch"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OperatorApplicationDetail"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/operator/applications/{application_id}/applicants/{applicant_id}/deliverables":{"patch":{"tags":["operator"],"summary":"Write an applicant's scalar deliverables (merge, validated by the pin)","description":"Persist the operator-entered per-applicant выдача (slice DLV3).\n\nThe write half of the configurable delivery (DLV1/DLV2): the operator fills the\n``per_applicant`` scalar items of the PINNED direction spec — the registration\ncode, the validity window, the entry checkpoint. Merge semantics (present keys\nwin, an explicit ``null`` clears), validated against the pin exactly as an\nintake patch is: an unknown key is a 422, a value failing its type or its L1\nvalidators is a 422 carrying every error, and ``visible_if``/``required_if``\nresolve through the shared ``hidden ⇒ not required`` rule. File items are NOT\nwritable here — their bytes go through the operator presign/confirm pair.","operationId":"operator_patch_applicant_deliverables_operator_applications__application_id__applicants__applicant_id__deliverables_patch","parameters":[{"name":"application_id","in":"path","required":true,"schema":{"type":"string","title":"Application Id"}},{"name":"applicant_id","in":"path","required":true,"schema":{"type":"string","title":"Applicant Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeliverablesPatch"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OperatorApplicationDetail"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/operator/applications/{application_id}/deliverables":{"patch":{"tags":["operator"],"summary":"Write the case-level scalar deliverables (merge, validated by the pin)","description":"Persist the operator-entered CASE-level выдача (slice DLV3).\n\nThe twin of the per-applicant seam for the direction's ``per_applicant=false``\nitems — one invoice reference, one status-check note for the whole booking. The\ntwo scopes are disjoint by construction: a per-applicant key sent here (or the\nreverse) is an unknown key, never a value parked in the wrong row.","operationId":"operator_patch_case_deliverables_operator_applications__application_id__deliverables_patch","parameters":[{"name":"application_id","in":"path","required":true,"schema":{"type":"string","title":"Application Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeliverablesPatch"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OperatorApplicationDetail"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/operator/applications/{application_id}/events":{"get":{"tags":["operator"],"summary":"Timeline from application_events","description":"The case timeline, newest first, from the append-only event log. Requires a staff session with role `operator` or higher; market-scoped.","operationId":"operator_timeline_operator_applications__application_id__events_get","parameters":[{"name":"application_id","in":"path","required":true,"schema":{"type":"string","title":"Application Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TimelineResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/auth/google/login":{"get":{"tags":["auth"],"summary":"Begin Google OAuth (returns the authorization URL)","description":"Starts the applicant sign-in: returns Google's consent URL and plants a signed, httpOnly CSRF `state` cookie that the callback must see echoed back. Redirect the browser to the returned URL. No authentication required.","operationId":"auth_google_login_auth_google_login_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LoginStartResponse"}}}}}}},"/auth/google/callback":{"get":{"tags":["auth"],"summary":"Google OAuth callback (establishes a session)","description":"Completes Google OAuth: verifies the `state` cookie, exchanges `code`, creates the applicant on first sign-in and sets the httpOnly applicant session cookie. Returns the session view. Mismatched or missing `state` is refused. No authentication required.","operationId":"auth_google_callback_auth_google_callback_get","parameters":[{"name":"code","in":"query","required":true,"schema":{"type":"string","title":"Code"}},{"name":"state","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"State"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SessionResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/auth/session":{"get":{"tags":["auth"],"summary":"Current session (applicant or anonymous)","description":"Reflects the caller's session cookie: the signed-in applicant identity, or `authenticated: false` when there is no valid cookie (a tampered or expired cookie reads as anonymous, never as an error). No authentication required.","operationId":"auth_session_auth_session_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SessionResponse"}}}}}}},"/auth/logout":{"post":{"tags":["auth"],"summary":"Destroy the current session","description":"Clears both the applicant and the staff session cookies; idempotent and safe to call when already signed out. No authentication required.","operationId":"auth_logout_auth_logout_post","responses":{"204":{"description":"Successful Response"}}}},"/auth/login-link/request":{"post":{"tags":["auth"],"summary":"Ask for a magic login link (always accepted, never says who exists)","description":"Requests a one-time e-mail login link for the given address. Always answers 202 with the same body whether or not an account exists (no account enumeration). Rate-limited per address (`login_link_email`, 3 per 15 min) and per IP (`login_link_ip`, 10 per hour); over budget answers 429 with `Retry-After`. No authentication required.","operationId":"auth_login_link_request_auth_login_link_request_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LoginLinkRequestBody"}}},"required":true},"responses":{"202":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LoginLinkRequestResponse"}}}},"429":{"description":"Address or source budget spent, or limiter down"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/auth/login-link/redeem":{"post":{"tags":["auth"],"summary":"Spend a magic login link token for an applicant session","description":"Redeems the token from a login-link e-mail: burns it and sets the httpOnly applicant session cookie. Every non-granting case (unknown, expired, spent or foreign token) is one flat 401. Rate-limited per IP (`login_link_redeem_ip`, 30 per 60 s) with 429 + `Retry-After`. No authentication required.","operationId":"auth_login_link_redeem_auth_login_link_redeem_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LoginLinkRedeemBody"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LoginLinkRedeemResponse"}}}},"401":{"description":"Unknown, expired, spent or foreign token — one shape"},"429":{"description":"Per-IP redeem budget spent, or the limiter is down"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/auth/staff/login":{"post":{"tags":["auth"],"summary":"Staff password login (step 1 of 2)","description":"First factor of the staff sign-in: checks e-mail + password and returns a short-lived TOTP challenge, never a session. Wrong credentials are one flat 401. Fail-closed brute-force budget of 5 attempts per 15 min per login; over budget answers 429 with `Retry-After`. No authentication required.","operationId":"auth_staff_login_auth_staff_login_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/StaffLoginRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StaffLoginResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/auth/staff/totp":{"post":{"tags":["auth"],"summary":"Staff TOTP verification (step 2 of 2)","description":"Second factor of the staff sign-in: verifies the TOTP code against the challenge from `/auth/staff/login` and sets the httpOnly staff session cookie. Fail-closed budget of 5 attempts per 5 min per challenge; over budget answers 429 with `Retry-After`. Public: the challenge token is the only credential presented.","operationId":"auth_staff_totp_auth_staff_totp_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/StaffTotpRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StaffSessionResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/auth/staff/telegram-link":{"post":{"tags":["auth"],"summary":"Issue a one-time code to link a Telegram account","description":"Issues a one-time code binding the caller's Telegram account to their staff identity. Not implemented yet (answers 501). Requires a staff session.","operationId":"auth_staff_telegram_link_auth_staff_telegram_link_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TelegramLinkResponse"}}}}}}},"/billing/setup-intent":{"post":{"tags":["billing"],"summary":"Create a SetupIntent to attach a card","description":"Reserved: create a PSP SetupIntent to save a card for later charges. Not implemented yet; currently fails. Requires an applicant session; owner-only.","operationId":"billing_setup_intent_billing_setup_intent_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SetupIntentResponse"}}}}}}},"/billing/checkout":{"post":{"tags":["billing"],"summary":"Application-bound checkout (one_off legs or subscription + gov_fee)","description":"Start payment for an own application from its priced quote: either the one-off legs, or a market subscription plus the government fee. Fixes the application's `purchase_mode` and returns the PSP next action. Rate limits `billing_checkout` 15/min per user and `billing_checkout_ip` 75/min per IP (429 + Retry-After). Requires an applicant session; owner-only.","operationId":"billing_checkout_billing_checkout_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CheckoutRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CheckoutResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/billing/subscriptions":{"post":{"tags":["billing"],"summary":"Subscription-first checkout (per (user, market))","description":"Start (or resume) the caller's subscription for a market, independent of any application, and return the PSP next action. One subscription per (user, market). Rate limits `billing_subscription` 15/min per user and `billing_subscription_ip` 75/min per IP (429 + Retry-After). Requires an applicant session; owner-only.","operationId":"billing_start_subscription_billing_subscriptions_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SubscriptionCheckoutRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SubscriptionCheckoutResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/billing/subscriptions/{market}":{"get":{"tags":["billing"],"summary":"Current subscription for a market","description":"The caller's subscription for a market (status, period, provider). 404 when there is none. Requires an applicant session; owner-only.","operationId":"billing_get_subscription_billing_subscriptions__market__get","parameters":[{"name":"market","in":"path","required":true,"schema":{"type":"string","title":"Market"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SubscriptionView"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"tags":["billing"],"summary":"Cancel a market subscription","description":"Cancel the caller's subscription for a market at the PSP that opened it, then mark it `cancelled`; idempotent for an already-cancelled one. 404 when there is none. Requires an applicant session; owner-only.","operationId":"billing_cancel_subscription_billing_subscriptions__market__delete","parameters":[{"name":"market","in":"path","required":true,"schema":{"type":"string","title":"Market"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SubscriptionView"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/billing/applications/{application_id}/payments":{"get":{"tags":["billing"],"summary":"Payments (and refunds) for an application","description":"Every payment and refund recorded for an own application, oldest first, with the order state. Requires an applicant session; owner-only.","operationId":"billing_list_payments_billing_applications__application_id__payments_get","parameters":[{"name":"application_id","in":"path","required":true,"schema":{"type":"string","title":"Application Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PaymentListResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/webhooks/stripe":{"post":{"tags":["billing"],"summary":"Stripe webhook (signature-verified, idempotent by event id)","description":"Stripe's server-to-server callback, not a client endpoint. No session: authentication is the `Stripe-Signature` header verified against the webhook secret (400 when missing or invalid). Events are processed once by event id; a repeat is acknowledged without side effects.","operationId":"billing_stripe_webhook_webhooks_stripe_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookAck"}}}}}}},"/admin/campaign-promo-codes":{"get":{"tags":["campaign-promos"],"summary":"List Campaign Promos","description":"Lists shared campaign promo codes with their activation usage. Requires a staff session with role `admin` or higher.","operationId":"list_campaign_promos_admin_campaign_promo_codes_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CampaignPromoList"}}}}}},"post":{"tags":["campaign-promos"],"summary":"Create Campaign Promo","description":"Mints a shared campaign promo code with a discount, activation cap and expiry. Requires a staff session with role `admin` or higher.","operationId":"create_campaign_promo_admin_campaign_promo_codes_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CampaignPromoCreate"}}},"required":true},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CampaignPromoView"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/admin/catalog":{"get":{"tags":["config-studio"],"summary":"List directions / markets / overlays with published version + draft flag","description":"Config Studio overview: every catalog target with its published version, status and whether a draft is open. A `layer` is one of `market` (market profile), `direction` (visa direction) or `overlay` (a market x direction patch); `target` is the slug within that layer. The list is filtered to the caller's direction/market grant. Requires a staff session with role `direction_lead` or higher.","operationId":"catalog_overview_admin_catalog_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CatalogOverview"}}}}}}},"/admin/catalog/{layer}/schema":{"get":{"tags":["config-studio"],"summary":"JSON Schema of a config layer's engine model (raw-editor autocomplete)","description":"The live JSON Schema of a layer's config model, for editor autocomplete. Pure read, no database. 404 for an unknown layer. Requires a staff session with role `direction_lead` or higher.","operationId":"layer_schema_admin_catalog__layer__schema_get","parameters":[{"name":"layer","in":"path","required":true,"schema":{"type":"string","title":"Layer"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LayerSchemaView"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/admin/catalog/{layer}/{target}/drafts":{"get":{"tags":["config-studio"],"summary":"The active draft for a target","description":"The single open draft of a target (payload, base version, author). 404 when none is open. Requires a staff session with role `direction_lead` or higher; direction-scoped.","operationId":"get_draft_admin_catalog__layer___target__drafts_get","parameters":[{"name":"layer","in":"path","required":true,"schema":{"type":"string","title":"Layer"}},{"name":"target","in":"path","required":true,"schema":{"type":"string","title":"Target"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DraftView"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"post":{"tags":["config-studio"],"summary":"Open the active draft for a target","description":"Open a new draft with the given payload, pinned to `base_version`. 409 if a draft is already open. Opening the first draft of a brand-new target (no version history) requires an admin role (403 otherwise). Requires a staff session with role `direction_lead` or higher; direction-scoped.","operationId":"create_draft_admin_catalog__layer___target__drafts_post","parameters":[{"name":"layer","in":"path","required":true,"schema":{"type":"string","title":"Layer"}},{"name":"target","in":"path","required":true,"schema":{"type":"string","title":"Target"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DraftCreate"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DraftView"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"put":{"tags":["config-studio"],"summary":"Re-save the active draft (optimistic-locked on base_version)","description":"Replace the draft payload. Optimistic lock: `expected_base_version` must match the draft's pin, else 409. 404 when no draft is open. Requires a staff session with role `direction_lead` or higher; direction-scoped.","operationId":"update_draft_admin_catalog__layer___target__drafts_put","parameters":[{"name":"layer","in":"path","required":true,"schema":{"type":"string","title":"Layer"}},{"name":"target","in":"path","required":true,"schema":{"type":"string","title":"Target"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DraftUpdate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DraftView"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"tags":["config-studio"],"summary":"Discard the active draft (idempotent)","description":"Delete the open draft of a target; 204 whether or not one existed. Requires a staff session with role `direction_lead` or higher; direction-scoped.","operationId":"delete_draft_admin_catalog__layer___target__drafts_delete","parameters":[{"name":"layer","in":"path","required":true,"schema":{"type":"string","title":"Layer"}},{"name":"target","in":"path","required":true,"schema":{"type":"string","title":"Target"}}],"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/admin/catalog/{layer}/{target}/drafts/from-published":{"post":{"tags":["config-studio"],"summary":"Seed the active draft from the latest published version (idempotent)","description":"Fork the latest published body of a target into an editable draft. Idempotent: returns the already-open draft unchanged. 404 when the target has no published version. Requires a staff session with role `direction_lead` or higher; direction-scoped.","operationId":"seed_draft_from_published_route_admin_catalog__layer___target__drafts_from_published_post","parameters":[{"name":"layer","in":"path","required":true,"schema":{"type":"string","title":"Layer"}},{"name":"target","in":"path","required":true,"schema":{"type":"string","title":"Target"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DraftView"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/admin/catalog/{layer}/{target}/drafts/rebase":{"post":{"tags":["config-studio"],"summary":"Rebase the active draft's base_version onto the current published version","description":"Move the draft's `base_version` pin onto the currently published version without touching the payload, so a publish that 409'd on a concurrent publish can be retried with the edits kept. Requires a staff session with role `direction_lead` or higher; direction-scoped.","operationId":"rebase_draft_route_admin_catalog__layer___target__drafts_rebase_post","parameters":[{"name":"layer","in":"path","required":true,"schema":{"type":"string","title":"Layer"}},{"name":"target","in":"path","required":true,"schema":{"type":"string","title":"Target"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DraftRebaseRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DraftView"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/admin/catalog/{layer}/{target}/drafts/{draft_id}/validate":{"post":{"tags":["config-studio"],"summary":"Structural validation of a draft against the live catalog (no mutation)","description":"Validate a draft against the live catalog and return `ok` plus errors and warnings. Read-only and idempotent. Requires a staff session with role `direction_lead` or higher; direction-scoped.","operationId":"validate_draft_route_admin_catalog__layer___target__drafts__draft_id__validate_post","parameters":[{"name":"layer","in":"path","required":true,"schema":{"type":"string","title":"Layer"}},{"name":"target","in":"path","required":true,"schema":{"type":"string","title":"Target"}},{"name":"draft_id","in":"path","required":true,"schema":{"type":"string","title":"Draft Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationResultView"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/admin/catalog/{layer}/{target}/drafts/{draft_id}/preview":{"post":{"tags":["config-studio"],"summary":"Compiled-flow preview + structural diff of a draft (no mutation)","description":"Compile the draft as if published and return the resulting flow preview and a structural diff against the published version. Read-only. Requires a staff session with role `direction_lead` or higher; direction-scoped.","operationId":"preview_draft_route_admin_catalog__layer___target__drafts__draft_id__preview_post","parameters":[{"name":"layer","in":"path","required":true,"schema":{"type":"string","title":"Layer"}},{"name":"target","in":"path","required":true,"schema":{"type":"string","title":"Target"}},{"name":"draft_id","in":"path","required":true,"schema":{"type":"string","title":"Draft Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PreviewResultView"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/admin/catalog/{layer}/{target}/drafts/{draft_id}/publish":{"post":{"tags":["config-studio"],"summary":"Publish a draft as a new immutable version","description":"Publish the draft as the next immutable version and close it. 409 when the published version moved under the draft (rebase first), when the draft changed concurrently, or when a field would stop being sensitive without `confirm_desensitize`; 422 when the draft fails validation. Requires a staff session with role `direction_lead` or higher; direction-scoped.","operationId":"publish_draft_route_admin_catalog__layer___target__drafts__draft_id__publish_post","parameters":[{"name":"layer","in":"path","required":true,"schema":{"type":"string","title":"Layer"}},{"name":"target","in":"path","required":true,"schema":{"type":"string","title":"Target"}},{"name":"draft_id","in":"path","required":true,"schema":{"type":"string","title":"Draft Id"}}],"requestBody":{"content":{"application/json":{"schema":{"anyOf":[{"$ref":"#/components/schemas/PublishRequest"},{"type":"null"}],"title":"Body"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublishResultView"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/admin/catalog/{layer}/{target}/versions":{"get":{"tags":["config-studio"],"summary":"Immutable version history of a target (newest first)","description":"All published and archived versions of a target with status, publisher and timestamp, newest first. Requires a staff session with role `direction_lead` or higher; direction-scoped.","operationId":"version_history_route_admin_catalog__layer___target__versions_get","parameters":[{"name":"layer","in":"path","required":true,"schema":{"type":"string","title":"Layer"}},{"name":"target","in":"path","required":true,"schema":{"type":"string","title":"Target"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/VersionHistoryView"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/admin/catalog/{layer}/{target}/versions/{version}":{"get":{"tags":["config-studio"],"summary":"The config body (payload) of one immutable version (read-only)","description":"The stored config payload of one version, regardless of its status. 404 for an unknown version. Requires a staff session with role `direction_lead` or higher; direction-scoped.","operationId":"version_body_route_admin_catalog__layer___target__versions__version__get","parameters":[{"name":"layer","in":"path","required":true,"schema":{"type":"string","title":"Layer"}},{"name":"target","in":"path","required":true,"schema":{"type":"string","title":"Target"}},{"name":"version","in":"path","required":true,"schema":{"type":"integer","title":"Version"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/VersionBodyView"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/admin/catalog/{layer}/{target}/diff":{"post":{"tags":["config-studio"],"summary":"Structural diff between two immutable versions' bodies (no mutation)","description":"Structural diff of the bodies of two versions named in the request body. Read-only; POST only because it carries two version numbers. Requires a staff session with role `direction_lead` or higher; direction-scoped.","operationId":"version_diff_route_admin_catalog__layer___target__diff_post","parameters":[{"name":"layer","in":"path","required":true,"schema":{"type":"string","title":"Layer"}},{"name":"target","in":"path","required":true,"schema":{"type":"string","title":"Target"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/VersionDiffRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/VersionDiffView"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/admin/catalog/{layer}/{target}/rollback":{"post":{"tags":["config-studio"],"summary":"Republish an old version's body as a new immutable version","description":"Roll back by republishing the body of an earlier version as a new version (history is never rewritten). 404 for an unknown version, 409 when a sensitive-field downgrade needs `confirm_desensitize`, 422 when the old body no longer validates. Requires a staff session with role `direction_lead` or higher; direction-scoped.","operationId":"rollback_route_admin_catalog__layer___target__rollback_post","parameters":[{"name":"layer","in":"path","required":true,"schema":{"type":"string","title":"Layer"}},{"name":"target","in":"path","required":true,"schema":{"type":"string","title":"Target"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RollbackRequest"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublishResultView"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/admin/catalog/{layer}/{target}/archive":{"post":{"tags":["config-studio"],"summary":"Soft-archive a target (hide from the public picker; reversible)","description":"Mark every version of a target archived so it leaves the public catalog; returns the number of affected versions. 404 for an unknown target, 409 with reasons when something still depends on it. Requires a staff session with role `direction_lead` or higher; direction-scoped.","operationId":"archive_route_admin_catalog__layer___target__archive_post","parameters":[{"name":"layer","in":"path","required":true,"schema":{"type":"string","title":"Layer"}},{"name":"target","in":"path","required":true,"schema":{"type":"string","title":"Target"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ArchiveResultView"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/admin/catalog/{layer}/{target}/unarchive":{"post":{"tags":["config-studio"],"summary":"Restore a soft-archived target (reappears in the public picker)","description":"Undo an archive: the target's versions become published again and it returns to the public catalog. 404 for an unknown target. Requires a staff session with role `direction_lead` or higher; direction-scoped.","operationId":"unarchive_route_admin_catalog__layer___target__unarchive_post","parameters":[{"name":"layer","in":"path","required":true,"schema":{"type":"string","title":"Layer"}},{"name":"target","in":"path","required":true,"schema":{"type":"string","title":"Target"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ArchiveResultView"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/public/catalog":{"get":{"tags":["configs"],"summary":"Markets / locales / directions (for the content-system & picker)","description":"Returns the published catalog: every live market with its locales, currency and the directions (visa products) it offers. The entry point for building a picker or crawling the public surface. No authentication required. Shares the per-IP budget of the zero-auth read surface (policy `public_read_ip`, 300 requests per 60 s); over budget answers 429 with `Retry-After`.","operationId":"configs_catalog_public_catalog_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CatalogResponse"}}}}},"security":[{},{"ApiKeyBearer":[]}]}},"/markets/{market}/directions":{"get":{"tags":["configs"],"summary":"Directions available on a market (picker)","description":"Lists the published directions of one market with pricing summaries and whether express checkout is enabled; 404 for an unknown or archived market. No authentication required. Shares the per-IP budget of the zero-auth read surface (policy `public_read_ip`, 300 requests per 60 s); over budget answers 429 with `Retry-After`.","operationId":"configs_market_directions_markets__market__directions_get","parameters":[{"name":"market","in":"path","required":true,"schema":{"type":"string","title":"Market"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MarketDirectionsResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{},{"ApiKeyBearer":[]}]}},"/markets/{market}/directions/{slug}/flow":{"get":{"tags":["configs"],"summary":"describe-JSON for the <StageRenderer> (compiled, pinned)","description":"Returns the compiled questionnaire (`FlowDescription`) for a market + direction: stages, fields, validation and document requirements, composed from the latest published versions (optional `locale` selects the copy). 404 for an unknown or archived market/direction. No authentication required. Shares the per-IP budget of the zero-auth read surface (policy `public_read_ip`, 300 requests per 60 s); over budget answers 429 with `Retry-After`.","operationId":"configs_flow_markets__market__directions__slug__flow_get","parameters":[{"name":"market","in":"path","required":true,"schema":{"type":"string","title":"Market"}},{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}},{"name":"locale","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Locale"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FlowDescription"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{},{"ApiKeyBearer":[]}]}},"/markets/{market}/directions/{slug}/widget":{"get":{"tags":["configs"],"summary":"Embed-widget description for a (market, direction) pair","description":"The embed widget's form spec (slice EMB1).\n\nBuilt off the *published* catalog exactly like ``/flow`` above and gated by the\nsame single availability rule (``list_directions_for_market``): a pair the\nmarket does not offer is not embeddable either. A direction whose published\nversion declares no ``widget`` section simply has no widget → 404.\n\nThe 404 bodies stay deliberately opaque. This endpoint is anonymous and lives\non pages we do not own, so \"which version, published or not, carries which\nsection\" is not something a caller gets to enumerate from here.\n\nThe ladder itself lives in\n:func:`~trippy_core.configs.widget_resolution.resolve_widget_pair`, shared with\n``POST /embed/handoff`` (EMB2a): the two routes are the same catalog read one\nstep apart and may not disagree on which pairs exist.","operationId":"configs_widget_markets__market__directions__slug__widget_get","parameters":[{"name":"market","in":"path","required":true,"schema":{"type":"string","title":"Market"}},{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}},{"name":"locale","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Locale"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WidgetDescription"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{},{"ApiKeyBearer":[]}]}},"/developers/api-keys":{"post":{"tags":["developers"],"summary":"Issue a self-serve API key","description":"**Public.** Issues a `vf_live_…` key to the given e-mail address immediately — no confirmation mail, no approval. The key is returned **once**, in this response; only its hash is stored. Present it as `Authorization: Bearer <key>` on the public read endpoints (`/public/*`, `/markets/*`, `/promotions/current-offer`) to be metered per key (policy `public_read_key`, see `quota`) instead of per IP.\n\nLimits: 3 issuances per hour per IP (`429` with `Retry-After`), 5 active keys per e-mail address (`409`; revoke one first).","operationId":"issue_api_key_developers_api_keys_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiKeyIssueRequest"}}},"required":true},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiKeyIssued"}}}},"409":{"description":"The address already holds the maximum of active keys."},"429":{"description":"Issuance budget for this address spent; retry later."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/developers/api-keys/current":{"get":{"tags":["developers"],"summary":"Describe the presented API key","description":"**Public, key-authenticated.** Returns the prefix, label, issue and last-use time and the quota of the key in `Authorization: Bearer`. `401` when the header is missing, the key is unknown or it was revoked.","operationId":"current_api_key_developers_api_keys_current_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiKeyInfo"}}}},"401":{"description":"No, unknown or revoked key."}},"security":[{"ApiKeyBearer":[]}]},"delete":{"tags":["developers"],"summary":"Revoke the presented API key","description":"**Public, key-authenticated.** Revokes the key in `Authorization: Bearer`. It stops being accepted at once on this node and within 60 seconds everywhere; it no longer counts toward the owner's 5-key ceiling. `401` when the header is missing, the key is unknown or already revoked.","operationId":"revoke_api_key_developers_api_keys_current_delete","responses":{"204":{"description":"Successful Response"},"401":{"description":"No, unknown or revoked key."}},"security":[{"ApiKeyBearer":[]}]}},"/applications/{application_id}/documents/presign":{"post":{"tags":["documents"],"summary":"Presign a document upload (direct-to-R2 PUT)","description":"Mint a short-lived presigned PUT URL for uploading one document directly to object storage; confirm the upload afterwards. Rate-limited per actor (`doc_presign`, 60/60s) and per IP (`doc_presign_ip`, 300/60s); 429 with `Retry-After`. Requires an applicant session; owner-only.","operationId":"documents_presign_applications__application_id__documents_presign_post","parameters":[{"name":"application_id","in":"path","required":true,"schema":{"type":"string","title":"Application Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PresignRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PresignResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/applications/{application_id}/documents/{document_id}/confirm":{"post":{"tags":["documents"],"summary":"Confirm an upload completed (triggers validation + OCR-hook)","description":"Tell the API a presigned upload has landed; the object is validated, promoted to permanent storage and queued for OCR. Rate-limited per actor (`doc_confirm`, 120/60s) and per IP (`doc_confirm_ip`, 600/60s). Requires an applicant session; owner-only.","operationId":"documents_confirm_applications__application_id__documents__document_id__confirm_post","parameters":[{"name":"application_id","in":"path","required":true,"schema":{"type":"string","title":"Application Id"}},{"name":"document_id","in":"path","required":true,"schema":{"type":"string","title":"Document Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConfirmRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DocumentView"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/operator/applications/{application_id}/documents/presign":{"post":{"tags":["documents"],"summary":"Operator presign for a visa_result deliverable (direct-to-R2 PUT)","description":"Mint a presigned PUT URL for an operator-uploaded deliverable (visa result, insurance, …) against a case in the caller's grant. Requires a staff session; market-scoped.","operationId":"operator_documents_presign_operator_applications__application_id__documents_presign_post","parameters":[{"name":"application_id","in":"path","required":true,"schema":{"type":"string","title":"Application Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/OperatorPresignRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PresignResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/operator/applications/{application_id}/documents/{document_id}/confirm":{"post":{"tags":["documents"],"summary":"Operator confirm of a visa_result upload (validate + promote)","description":"Confirm an operator deliverable upload: validate, promote, and record the per-applicant outcome (a denial needs a reason, else 422). Requires a staff session; market-scoped.","operationId":"operator_documents_confirm_operator_applications__application_id__documents__document_id__confirm_post","parameters":[{"name":"application_id","in":"path","required":true,"schema":{"type":"string","title":"Application Id"}},{"name":"document_id","in":"path","required":true,"schema":{"type":"string","title":"Document Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConfirmRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DocumentView"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/operator/applications/{application_id}/documents/{document_id}/business-review":{"post":{"tags":["documents"],"summary":"Accept a document or request a replacement","description":"Record the operator's verdict on the current document version: accept it or ask the applicant for a replacement. `Idempotency-Key` is required (422 without it); a stale version is 409. Requires a staff session with role `operator` or higher; market-scoped.","operationId":"operator_document_business_review_operator_applications__application_id__documents__document_id__business_review_post","parameters":[{"name":"application_id","in":"path","required":true,"schema":{"type":"string","title":"Application Id"}},{"name":"document_id","in":"path","required":true,"schema":{"type":"string","title":"Document Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BusinessReviewRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BusinessReviewView"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/applications/{application_id}/documents/{document_id}":{"delete":{"tags":["documents"],"summary":"Delete a document","description":"Permanently delete one of the caller's own uploads (pending and promoted object alike). Evidence of a submitted case is frozen: 409. Requires an applicant session; owner-only.","operationId":"documents_delete_applications__application_id__documents__document_id__delete","parameters":[{"name":"application_id","in":"path","required":true,"schema":{"type":"string","title":"Application Id"}},{"name":"document_id","in":"path","required":true,"schema":{"type":"string","title":"Document Id"}}],"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/documents/{document_id}/download":{"get":{"tags":["documents"],"summary":"Presigned GET for a document (audit-logged, TTL 5 min)","description":"A fresh presigned GET URL (5-minute TTL) for a verified document. Rate-limited per actor (`doc_download`, 30/60s). Requires any signed-in session: the owning applicant or an in-scope operator. Every read is audit-logged.","operationId":"documents_download_documents__document_id__download_get","parameters":[{"name":"document_id","in":"path","required":true,"schema":{"type":"string","title":"Document Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DownloadResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/documents/{document_id}/prefill":{"get":{"tags":["documents"],"summary":"Owner bio-page prefill from a confirmed passport scan (ADR-17, §10)","description":"The OCR prefill extracted from a confirmed passport scan: non-sensitive answers plus the passport number as a last-4 mask only. Shares the `doc_download` budget (30/60s). Requires any signed-in session: the owning applicant or an in-scope operator. Every read is audit-logged.","operationId":"documents_prefill_documents__document_id__prefill_get","parameters":[{"name":"document_id","in":"path","required":true,"schema":{"type":"string","title":"Document Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OcrPrefill"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/documents/{document_id}/preview":{"get":{"tags":["documents"],"summary":"Inline PNG preview of a document's first page (audit-logged)","description":"A rasterised PNG of the document's first page, safe to render inline. Shares the `doc_download` budget (30/60s). Requires any signed-in session: the owning applicant or an in-scope operator. Every read is audit-logged.","operationId":"documents_preview_documents__document_id__preview_get","parameters":[{"name":"document_id","in":"path","required":true,"schema":{"type":"string","title":"Document Id"}}],"responses":{"200":{"description":"Successful Response","content":{"image/png":{}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/staff/applications/{application_id}/dossier-generations":{"post":{"tags":["dossiers"],"summary":"Generate or regenerate the pinned self-file application package","description":"Generate (or regenerate) the printable self-file dossier for a case from its pinned flow. Returns the generation record (201); a case not ready for a dossier is 409. Requires a staff session with role `operator` or higher; market-scoped.","operationId":"generate_dossier_staff_applications__application_id__dossier_generations_post","parameters":[{"name":"application_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Application Id"}},{"name":"Idempotency-Key","in":"header","required":true,"schema":{"type":"string","minLength":1,"maxLength":200,"title":"Idempotency-Key"}}],"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DossierGenerationView"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/embed/handoff":{"post":{"tags":["embed"],"summary":"Park an embed-widget submission and hand back a one-shot token","description":"Validate, prove and park one widget submission (see the module docstring).","operationId":"embed_handoff_embed_handoff_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmbedHandoffRequest"}}},"required":true},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmbedHandoffResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/embed/claim":{"post":{"tags":["embed"],"summary":"Redeem a one-shot embed handoff token into the signed-in session","description":"Burn the token once and hand back what the widget parked (see module docstring).\n\nThe burn, the attach of the consent proof to this user and the hand-back are one\ntransaction: it commits only on the success path, so a refused claim leaves the\ntoken unburned and the proof unattached.","operationId":"embed_claim_embed_claim_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmbedClaimRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmbedClaimResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/billing/express/start":{"post":{"tags":["express"],"summary":"Start a guest express checkout (anonymous, service leg only)","description":"Starts a pay-first checkout for a guest: creates the case and order for the given market/direction/e-mail and returns the payment session to redirect to, setting a start-secret cookie the status poll must present. Rate-limited per IP (`express_start_ip`, 100 per 60 s) with 429 + `Retry-After`. No authentication required.","operationId":"express_start_billing_express_start_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ExpressStartRequest"}}},"required":true},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ExpressStartResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/billing/express/{express_id}/status":{"get":{"tags":["express"],"summary":"Poll a guest express checkout (hands out the session once paid)","description":"Polls the state of an express checkout. Once payment is confirmed by reconciliation and the browser presents the start-secret cookie, the response also sets the applicant session cookie; otherwise only a state word. No authentication required.","operationId":"express_status_billing_express__express_id__status_get","parameters":[{"name":"express_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Express Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ExpressStatusResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/operator/prepaid-drafts":{"get":{"tags":["express","operator"],"summary":"List prepaid applications still sitting in DRAFT (read-only)","description":"Applications whose service fee is captured but which are still in DRAFT (typically express checkouts not yet filled in), paginated, with the captured total. Requires a staff session with role `operator` or higher; market-scoped.","operationId":"prepaid_drafts_list_operator_prepaid_drafts_get","parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":200,"minimum":1,"default":50,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0,"title":"Offset"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PrepaidDraftList"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/public/fx-rates":{"get":{"tags":["fx"],"summary":"Latest complete ECB reference-rate snapshot (display only)","description":"Returns the latest complete ECB reference-rate snapshot for display purposes (not a quote). Cached 5 minutes (`Cache-Control`, `ETag`); 503 while no complete snapshot exists. No authentication required. Rate-limited per IP by its own budget (`public_fx_snapshot`, 120 per 60 s) on top of `public_read_ip` (300 per 60 s); over budget answers 429 with `Retry-After`.","operationId":"public_fx_snapshot_public_fx_rates_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicFxSnapshot"}}}},"503":{"description":"No complete reference-rate snapshot available"}},"security":[{},{"ApiKeyBearer":[]}]}},"/public/markets/{market}/landings/{locale}":{"get":{"tags":["landings"],"summary":"Landing config for a (market, locale, variant)","description":"Returns the landing-page content for a market and locale (`variant` selects an A/B variant). Not implemented yet (answers 501). No authentication required. Shares the per-IP budget of the zero-auth read surface (policy `public_read_ip`, 300 requests per 60 s); over budget answers 429 with `Retry-After`.","operationId":"landings_config_public_markets__market__landings__locale__get","parameters":[{"name":"market","in":"path","required":true,"schema":{"type":"string","title":"Market"}},{"name":"locale","in":"path","required":true,"schema":{"type":"string","title":"Locale"}},{"name":"variant","in":"query","required":false,"schema":{"type":"string","default":"default","title":"Variant"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LandingConfigResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{},{"ApiKeyBearer":[]}]}},"/public/markets/{market}/directions/{slug}/seo/{locale}":{"get":{"tags":["landings"],"summary":"Direction SEO page (hero/FAQ/pricing/hreflang)","description":"Returns the SEO page content of a direction (hero, FAQ, pricing, hreflang alternates) for one locale. Not implemented yet (answers 501). No authentication required. Shares the per-IP budget of the zero-auth read surface (policy `public_read_ip`, 300 requests per 60 s); over budget answers 429 with `Retry-After`.","operationId":"landings_seo_page_public_markets__market__directions__slug__seo__locale__get","parameters":[{"name":"market","in":"path","required":true,"schema":{"type":"string","title":"Market"}},{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}},{"name":"locale","in":"path","required":true,"schema":{"type":"string","title":"Locale"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SeoPageResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{},{"ApiKeyBearer":[]}]}},"/operator/outbox/dead-letters":{"get":{"tags":["operator"],"summary":"List dead-lettered outbox rows (paginated) + badge count","description":"Outbox events that exhausted their delivery attempts: ids, event type, attempt count and last error (no PII), with the total for a badge. Requires a staff session with role `operator` or higher.","operationId":"dead_letters_list_operator_outbox_dead_letters_get","parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":200,"minimum":1,"default":50,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0,"title":"Offset"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeadLetterList"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/operator/outbox/dead-letters/{event_id}/retry":{"post":{"tags":["operator"],"summary":"Manually retry a dead-lettered outbox row (audit-logged)","description":"Put one dead-lettered event back on the delivery queue; the action is audit-logged. 404 when the id is unknown or the row is not dead-lettered. Requires a staff session with role `operator` or higher.","operationId":"dead_letter_retry_operator_outbox_dead_letters__event_id__retry_post","parameters":[{"name":"event_id","in":"path","required":true,"schema":{"type":"integer","title":"Event Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RetryResult"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/promotions/me":{"get":{"tags":["promotions"],"summary":"Current user's promotion inventory","description":"Lists the caller's promo codes (referral rewards) with status and expiry. Requires an applicant session; owner-only.","operationId":"promotion_inventory_promotions_me_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PromoInventoryResponse"}}}}}}},"/promotions/current-offer":{"get":{"tags":["promotions"],"summary":"Current anonymous link offer","description":"Resolve the httpOnly cookie for a client island; every refusal is empty.","operationId":"current_campaign_offer_promotions_current_offer_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CampaignOfferResponse"}}}}},"security":[{},{"ApiKeyBearer":[]}]}},"/promotions/applications/{application_id}/offer":{"get":{"tags":["promotions"],"summary":"Current application's reserved link offer","description":"Returns the campaign offer reserved for one of the caller's applications, or an empty offer. Not cacheable (`private, no-store`). Requires an applicant session; owner-only.","operationId":"application_campaign_offer_promotions_applications__application_id__offer_get","parameters":[{"name":"application_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Application Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CampaignOfferResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/referrals/me":{"get":{"tags":["referrals"],"summary":"Current user's referral code and invite summary","description":"Returns the caller's referral code and invite counters (invited, pending, qualified, rewarded). Requires an applicant session; owner-only.","operationId":"referral_summary_referrals_me_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReferralSummaryResponse"}}}}}}},"/referrals/claim":{"post":{"tags":["referrals"],"summary":"Claim a referral code before the first conversion","description":"Attributes the caller to a referrer's code; only possible before the caller's first conversion and only once. Requires an applicant session; owner-only.","operationId":"referral_claim_referrals_claim_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReferralClaimRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReferralClaimResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/staff/me":{"get":{"tags":["staff"],"summary":"Read your own self-serve profile flags (Telegram link status)","description":"Returns the caller's own self-serve flags: whether Telegram is linked and the working hours. Requires a staff session with role `operator` or higher.","operationId":"read_me_staff_me_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StaffMe"}}}}}}},"/staff/me/telegram/link-token":{"post":{"tags":["staff"],"summary":"Mint a one-time deep-link to bind your Telegram account","description":"Mints a one-time Telegram deep-link that binds the bot chat to the caller's staff identity. Requires a staff session with role `operator` or higher.","operationId":"issue_telegram_link_token_staff_me_telegram_link_token_post","responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StaffTelegramLinkToken"}}}}}}},"/staff/me/telegram":{"delete":{"tags":["staff"],"summary":"Unlink your Telegram account (and invalidate unspent link tokens)","description":"Removes the caller's Telegram binding and invalidates any unspent link tokens; audit-logged. Requires a staff session with role `operator` or higher.","operationId":"unlink_telegram_staff_me_telegram_delete","responses":{"204":{"description":"Successful Response"}}}},"/support/public/config":{"get":{"summary":"Public guest-support consent descriptor","description":"Return only the exact notice the enabled public form may acknowledge.","operationId":"support_public_config_support_public_config_get","parameters":[{"name":"market_code","in":"query","required":true,"schema":{"type":"string","minLength":2,"maxLength":16,"title":"Market Code"}},{"name":"locale","in":"query","required":true,"schema":{"type":"string","minLength":2,"maxLength":8,"title":"Locale"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicSupportConfig"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/support/threads":{"get":{"tags":["support"],"summary":"List the current user's support threads (ЛК)","description":"The caller's own support threads, newest activity first, keyset-paginated (`after` cursor is returned only on a full page). Requires an applicant session; owner-only.","operationId":"support_threads_list_support_threads_get","parameters":[{"name":"after","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"opaque keyset cursor","title":"After"},"description":"opaque keyset cursor"},{"name":"limit","in":"query","required":false,"schema":{"anyOf":[{"type":"integer","maximum":100,"minimum":1},{"type":"null"}],"title":"Limit"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SupportThreadList"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"post":{"tags":["support"],"summary":"Open a support thread (ЛК)","description":"Open a new support thread for a market with a first message. Rate limit `support_thread_create`: 10 per hour per user (429 + Retry-After). Requires an applicant session; owner-only.","operationId":"support_thread_create_support_threads_post","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SupportThreadCreate"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SupportThreadView"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/support/threads/{thread_id}/messages":{"get":{"tags":["support"],"summary":"Read a thread's transcript (public messages only; polled)","description":"Client-visible messages of an own thread, keyset-paginated. Designed for polling: the response carries an `ETag`, and `If-None-Match` yields 304 when nothing changed. Internal operator notes are never included. Requires an applicant session; owner-only.","operationId":"support_messages_list_support_threads__thread_id__messages_get","parameters":[{"name":"thread_id","in":"path","required":true,"schema":{"type":"string","title":"Thread Id"}},{"name":"after","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"opaque keyset cursor","title":"After"},"description":"opaque keyset cursor"},{"name":"limit","in":"query","required":false,"schema":{"anyOf":[{"type":"integer","maximum":100,"minimum":1},{"type":"null"}],"title":"Limit"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SupportMessageList"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"post":{"tags":["support"],"summary":"Send a message into an own thread","description":"Append a message to an own open thread. `client_message_id` deduplicates retries (a reused id by another message is 409); a closed thread is 409. Rate limit `support_message`: 60 per minute per user (429 + Retry-After). Requires an applicant session; owner-only.","operationId":"support_message_create_support_threads__thread_id__messages_post","parameters":[{"name":"thread_id","in":"path","required":true,"schema":{"type":"string","title":"Thread Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SupportMessageCreate"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SupportMessagePosted"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/support/threads/link-guest/preview":{"get":{"tags":["support"],"summary":"Preview the guest thread this browser could attach to the account","description":"Step 1 of the link, and it changes **nothing** (SPEC-SUPPORT §6.5, finding M7).","operationId":"support_guest_link_preview_support_threads_link_guest_preview_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GuestThreadLinkPreview"}}}}}}},"/support/threads/link-guest":{"post":{"tags":["support"],"summary":"Attach the previewed guest thread to the current account","description":"Step 2: the confirmation, and the only write in this pair.\n\n``body.thread_id`` must be the thread the cookie opens — the echo is what makes\nthe step *explicit*: it binds the outcome to the conversation the user was shown\n(a cookie swapped between preview and confirm links nothing), and it is why an\nattacker's tossed cookie has to get past a human reading somebody else's\nconversation rather than past nobody at all.\n\nThe write itself is a CAS on ``user_id`` that also clears\n``guest_token_hash`` (:func:`~trippy_core.support.link.attach_thread_to_user`):\nownership never transfers away from an account, and the bearer token dies in the\nsame statement that makes the account the reader. The stale cookie is dropped\nfrom the browser too — it now names a thread it can no longer open, and the ЛК\nis the way in.","operationId":"support_guest_link_confirm_support_threads_link_guest_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/GuestThreadLinkConfirm"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GuestThreadLinkResult"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/operator/support/threads":{"get":{"tags":["operator"],"summary":"Operator support inbox (scoped market queue, keyset + counters)","description":"The support queue of the operator's markets with status / market / assignee / unread filters, sorting, keyset pagination and badge counters. Requires a staff session with role `operator` or higher; market-scoped.","operationId":"operator_support_list_operator_support_threads_get","parameters":[{"name":"status","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Status"}},{"name":"market","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Market"}},{"name":"assignee","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"staff id","title":"Assignee"},"description":"staff id"},{"name":"unread","in":"query","required":false,"schema":{"type":"boolean","description":"only threads needing eyes","default":false,"title":"Unread"},"description":"only threads needing eyes"},{"name":"sort","in":"query","required":false,"schema":{"type":"string","description":"recent | sla | unread (keyset paging: recent only)","default":"recent","title":"Sort"},"description":"recent | sla | unread (keyset paging: recent only)"},{"name":"after","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"opaque keyset cursor","title":"After"},"description":"opaque keyset cursor"},{"name":"limit","in":"query","required":false,"schema":{"anyOf":[{"type":"integer","maximum":100,"minimum":1},{"type":"null"}],"title":"Limit"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OperatorSupportThreadList"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/operator/support/threads/{thread_id}":{"get":{"tags":["operator"],"summary":"Operator thread card: thread + full transcript (internal included)","description":"One thread with its full transcript, internal notes included, keyset-paginated. 404 for a thread outside the operator's markets. Requires a staff session with role `operator` or higher; market-scoped.","operationId":"operator_support_get_operator_support_threads__thread_id__get","parameters":[{"name":"thread_id","in":"path","required":true,"schema":{"type":"string","title":"Thread Id"}},{"name":"after","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"opaque keyset cursor","title":"After"},"description":"opaque keyset cursor"},{"name":"limit","in":"query","required":false,"schema":{"anyOf":[{"type":"integer","maximum":100,"minimum":1},{"type":"null"}],"title":"Limit"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OperatorSupportThreadDetail"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/operator/support/threads/{thread_id}/messages":{"post":{"tags":["operator"],"summary":"Reply to the client, or write an internal note","description":"Post a staff message: `visibility` `public` reaches the client, `internal` is a note only staff see. 409 when the thread state refuses messages or the `client_message_id` is already taken. Requires a staff session with role `operator` or higher; market-scoped.","operationId":"operator_support_message_create_operator_support_threads__thread_id__messages_post","parameters":[{"name":"thread_id","in":"path","required":true,"schema":{"type":"string","title":"Thread Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/OperatorSupportMessageCreate"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SupportMessagePosted"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/operator/support/threads/{thread_id}/claim":{"post":{"tags":["operator"],"summary":"Claim the thread (atomic CAS on support_threads.assignee_id)","description":"Take ownership of an unassigned thread. Atomic: 409 naming the current assignee when someone else already holds it. Requires a staff session with role `operator` or higher; market-scoped.","operationId":"operator_support_claim_operator_support_threads__thread_id__claim_post","parameters":[{"name":"thread_id","in":"path","required":true,"schema":{"type":"string","title":"Thread Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SupportThreadClaimResult"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/operator/support/threads/{thread_id}/status":{"post":{"tags":["operator"],"summary":"Drive a thread event: resolve / close / spam / reopen (CAS)","description":"Apply a lifecycle event (`resolve`, `close`, `spam`, `reopen`) and return the updated thread. Pass `expected_status` for optimistic concurrency: a mismatch is 409. Requires a staff session with role `operator` or higher; market-scoped.","operationId":"operator_support_status_operator_support_threads__thread_id__status_post","parameters":[{"name":"thread_id","in":"path","required":true,"schema":{"type":"string","title":"Thread Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SupportThreadStatusUpdate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OperatorSupportThreadView"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/operator/support/threads/{thread_id}/read":{"post":{"tags":["operator"],"summary":"Stamp the thread as read by this operator (unread badge)","description":"Record that this operator has read the thread (clears the unread badge; the client sees `staff_read_at`). Requires a staff session with role `operator` or higher; market-scoped.","operationId":"operator_support_read_operator_support_threads__thread_id__read_post","parameters":[{"name":"thread_id","in":"path","required":true,"schema":{"type":"string","title":"Thread Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SupportThreadRead"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/support/public/threads":{"post":{"tags":["support"],"summary":"Open a support thread from the public form (guest, no account)","description":"The product's only unauthenticated write path (slice SUP7, §9/§10).\n\n**One transaction, one writer.** The thread, its KVKK proof and the first message\nare composed into a single unit of work, and the thread+proof half goes through\nthe SUP5 seam :func:`~trippy_core.support.intake.open_guest_thread` — the seam\nthat cannot be talked out of the consent (it raises before any INSERT). This\nhandler deliberately has no second path to a guest row: were it to call\n``create_thread`` directly, \"no consent → no thread\" would become a rule this\nmodule has to remember instead of one the seam enforces.\n\n**The token is minted onto the row, in the same transaction.** A guest token\nnames the thread it opens, and the thread has no id until it is INSERTed — while\n``ck_support_threads_has_channel`` demands the hash *at* insert. So the row is\nborn carrying the hash of a nonce that was never handed to anyone and the real\ntoken is minted onto it before the commit: no committed state ever carries the\nplaceholder, and the placeholder has no plaintext to leak.\n\n**Nothing is e-mailed here.** The confirmation mail carrying the second carrier\n(the link that heals a lost cookie) is SUP10; this handler plants the cookie.\n\n**What is NOT here:** per-IP fail-closed limiters, trusted-proxy client IP, the\naggregate thread ceiling and Turnstile — the whole anti-spam perimeter is SUP7a,\nwhich is exactly why the boot guard forbids enabling this route outside ``local``\nuntil it lands.","operationId":"support_public_thread_create_support_public_threads_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicSupportThreadCreate"}}},"required":true},"responses":{"202":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicSupportThreadAccepted"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/support/public/threads/{thread_id}/messages":{"post":{"tags":["support"],"summary":"Send a follow-up into a guest thread (token-authenticated)","description":"A guest's follow-up, fenced by the same token as the read (SUP7).\n\nBehind the intake flag too: it is the same unauthenticated writer with the same\nmissing perimeter, and an open thread whose reply box stays open when the form is\nswitched off would be a way around the switch.\n\nThe per-thread ceiling (``support_thread_max_messages``, §10.5) is enforced here\nrather than in the service: an operator answering a long thread must never be\nthe one who hits the cap, and the authenticated client path has its own\nper-actor budget (H10). Over the cap the answer is a 409 — the request was\nunderstood and is not retryable as-is, which is what a stuck retry loop should\nhear.","operationId":"support_public_message_create_support_public_threads__thread_id__messages_post","parameters":[{"name":"thread_id","in":"path","required":true,"schema":{"type":"string","title":"Thread Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicSupportMessageCreate"}}}},"responses":{"202":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicSupportMessageAccepted"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/support/public/threads/{thread_id}":{"get":{"tags":["support"],"summary":"Read a guest thread by its signed token (404 unless it matches)","description":"404 for everybody except the holder of this thread's guest token.\n\n**Two carriers, one check.** The token arrives either in the ``token`` query\nparameter (the one-time link in the confirmation e-mail) or in the httpOnly\ncookie; the link wins, because it is the carrier that *heals* the other —\na guest arriving from mail on a second device (or after clearing cookies)\ngets the cookie re-planted here and keeps polling without it. Without that\nsecond carrier he would lose the conversation forever (finding C4).\n\nEvery failure — no token, forged signature, expired, revoked, or a valid\ntoken for a *different* thread — is the same flat 404 as a thread that does\nnot exist. The UUID in the URL authenticates nothing on its own.\n\n**The transcript rides along (slice SUP7, tail of SUP6).** SUP6 served the\nthread row alone, which made the spec's own decision — a forwarded mail hands\nover the *conversation* — unimplemented, and showed the guest a thread with no\nmessages in it. The page comes from the public-only SELECT\n(:meth:`SupportRepository.list_public_messages`) rendered into\n:class:`SupportMessageView`, the shape with **no ``visibility`` field**: the same\ntwo independent fences the account path has, because a guest is the reader with\nthe least standing to see operators' internal notes about him.\n\n**A validator as wide as the representation, still as cheap (cf. SUP4).** The guest\npolls this exactly as the ЛК does, so a matching ``If-None-Match`` returns 304\n*before* the page is materialised — but this route answers with the thread row *and*\nits page, so the digest covers every projected thread field plus the page identity\n(``after``/``limit``) and the head message, not just ``(last public message id,\nstaff_read_at)``: that narrow pair would hand a polling guest a stale\n``status=\"open\"`` after an operator resolved the thread. It stays cheap because all\nof it is already loaded — the thread for the token check, the head as one\nindex-ordered row. Internal notes move nothing in it, so a poll cannot observe\ninternal activity through the validator either.\n\nStill **unlimited** on purpose-of-record: there is no rate limit on this route\nyet, so tokens can be guessed at line rate — that is the SUP7a per-IP limiter's\njob and the reason the intake flag cannot be turned on outside ``local``. Which\nis also why this read sits behind ``_require_public_intake`` like the two writes:\nsee that helper for why the gate is free today and why SUP7a, not this slice,\nlifts it.","operationId":"support_public_thread_get_support_public_threads__thread_id__get","parameters":[{"name":"thread_id","in":"path","required":true,"schema":{"type":"string","title":"Thread Id"}},{"name":"token","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"guest token from the e-mailed link","title":"Token"},"description":"guest token from the e-mailed link"},{"name":"after","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"opaque keyset cursor","title":"After"},"description":"opaque keyset cursor"},{"name":"limit","in":"query","required":false,"schema":{"anyOf":[{"type":"integer","maximum":100,"minimum":1},{"type":"null"}],"title":"Limit"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SupportPublicThread"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/traveller-profiles":{"get":{"tags":["travellers"],"summary":"List the caller's saved travellers (newest first)","description":"The caller's saved traveller profiles, newest first. Requires an applicant session; owner-only.","operationId":"traveller_profiles_list_traveller_profiles_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TravellerProfileList"}}}}}}},"/traveller-profiles/{profile_id}":{"get":{"tags":["travellers"],"summary":"One saved traveller (owner-scoped)","description":"One saved traveller profile. Requires an applicant session; owner-only.","operationId":"traveller_profiles_get_traveller_profiles__profile_id__get","parameters":[{"name":"profile_id","in":"path","required":true,"schema":{"type":"string","title":"Profile Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TravellerProfileView"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"tags":["travellers"],"summary":"Forget one saved traveller (owner-scoped)","description":"Delete a saved traveller profile. Requires an applicant session; owner-only.","operationId":"traveller_profiles_delete_traveller_profiles__profile_id__delete","parameters":[{"name":"profile_id","in":"path","required":true,"schema":{"type":"string","title":"Profile Id"}}],"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/traveller-profiles/{profile_id}/apply":{"post":{"tags":["travellers"],"summary":"Apply a saved traveller into one of the caller's draft applications","description":"Copy a saved traveller's answers into one of the caller's draft applications; the response carries the application's new `ETag`. A non-draft target is 409. Requires an applicant session; owner-only.","operationId":"traveller_profiles_apply_traveller_profiles__profile_id__apply_post","parameters":[{"name":"profile_id","in":"path","required":true,"schema":{"type":"string","title":"Profile Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApplyTravellerProfileRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApplyTravellerProfileResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/traveller-profiles/{profile_id}/documents":{"get":{"tags":["travellers"],"summary":"Verified documents this saved traveller can reuse into a new trip","description":"The verified documents attached to this saved traveller that can be reused in a new application. Requires an applicant session; owner-only.","operationId":"traveller_profile_documents_traveller_profiles__profile_id__documents_get","parameters":[{"name":"profile_id","in":"path","required":true,"schema":{"type":"string","title":"Profile Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReusableDocumentList"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/traveller-profiles/{profile_id}/documents/{document_id}/reuse":{"post":{"tags":["travellers"],"summary":"Copy one of a saved traveller's verified documents into a draft slot","description":"Copy one verified document of a saved traveller into a slot of a draft application. A non-draft target or an occupied slot is 409. Requires an applicant session; owner-only.","operationId":"traveller_profile_document_reuse_traveller_profiles__profile_id__documents__document_id__reuse_post","parameters":[{"name":"profile_id","in":"path","required":true,"schema":{"type":"string","title":"Profile Id"}},{"name":"document_id","in":"path","required":true,"schema":{"type":"string","title":"Document Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReuseDocumentRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReuseDocumentResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/me/profile":{"get":{"tags":["me"],"summary":"Read your reusable applicant profile","description":"Returns the caller's reusable applicant profile (the data prefilled into new cases); 404 until one has been saved. Requires an applicant session; owner-only.","operationId":"get_profile_me_profile_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserProfileView"}}}}}},"put":{"tags":["me"],"summary":"Create or fully replace your reusable applicant profile","description":"Creates or fully replaces the caller's reusable applicant profile (PUT semantics: omitted fields are cleared). Rate-limited per user (`profile_write`, 30 per 60 s) with 429 + `Retry-After`. Requires an applicant session; owner-only.","operationId":"put_profile_me_profile_put","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserProfileWrite"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserProfileView"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/me/telegram/link-token":{"post":{"tags":["me"],"summary":"Mint a one-time deep-link to bind your Telegram account","description":"Mints a one-time Telegram deep-link that binds the bot chat to the caller's account for notifications. Rate-limited per user (`tg_link_token`, 5 per 5 min) with 429 + `Retry-After`. Requires an applicant session; owner-only.","operationId":"issue_telegram_link_token_me_telegram_link_token_post","responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserTelegramLinkToken"}}}}}}},"/me/telegram":{"get":{"tags":["me"],"summary":"Whether your Telegram account is currently linked","description":"Report the binding as a single boolean — never the bound chat id.\n\nThe subject is always the session's own actor: there is no path/query/body\nparameter to point this at another account. Not audit-logged (a read of one's\nown flag), and a soft-deleted account reads ``linked=false``.","operationId":"get_telegram_status_me_telegram_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserTelegramStatus"}}}}}},"delete":{"tags":["me"],"summary":"Unlink your Telegram account (and invalidate unspent link tokens)","description":"Clear the binding, revoke every live link token, journal the act.\n\nIdempotent: unlinking an already-unlinked account is the same 204 and still\nrevokes tokens, so a deep-link minted moments earlier cannot resurrect the\nbinding. Every call — no-op included — writes exactly one\n``user.telegram_unlinked`` row with the request ip and a boolean-only diff.","operationId":"unlink_telegram_me_telegram_delete","responses":{"204":{"description":"Successful Response"}}}},"/go/{code}":{"get":{"tags":["utm-links"],"summary":"UTM ad-link: count the click and redirect to the target","description":"Counts one click on the short link and answers a 302 to its target URL with the UTM parameters attached (and a campaign offer cookie when the link carries a promo). Unknown codes answer 404. No authentication required.","operationId":"utm_link_redirect_go__code__get","parameters":[{"name":"code","in":"path","required":true,"schema":{"type":"string","title":"Code"}}],"responses":{"302":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/admin/utm-links":{"get":{"tags":["utm-links"],"summary":"List ad links with clicks / registrations / payments","description":"Lists tracking links with their funnel counters (clicks, registrations, payments). Requires a staff session with role `admin` or higher.","operationId":"admin_list_utm_links_admin_utm_links_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UtmLinkList"}}}}}},"post":{"tags":["utm-links"],"summary":"Mint a trackable ad link","description":"Creates a short tracking link (`/go/{code}`) with its UTM parameters and an optional campaign promo. Requires a staff session with role `admin` or higher.","operationId":"admin_create_utm_link_admin_utm_links_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UtmLinkCreate"}}},"required":true},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UtmLinkView"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}}},"components":{"schemas":{"AccountModel":{"type":"string","enum":["operator_account","applicant_account"],"title":"AccountModel","description":"Government-portal account model (arch §7 ``processing.account_model``)."},"ActionRequiredView":{"properties":{"info_request_id":{"type":"string","title":"Info Request Id"},"comment":{"type":"string","title":"Comment"},"items":{"items":{"$ref":"#/components/schemas/InfoRequestItemView"},"type":"array","title":"Items"},"responses":{"items":{"$ref":"#/components/schemas/InfoRequestResponseView"},"type":"array","title":"Responses"},"origin_status":{"anyOf":[{"$ref":"#/components/schemas/ClientStatus"},{"type":"null"}]}},"additionalProperties":false,"type":"object","required":["info_request_id","comment"],"title":"ActionRequiredView","description":"The NEEDS_INFO banner shown in the ЛК (arch §7)."},"AnalyticsEventIn":{"properties":{"name":{"type":"string","title":"Name"},"anon_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Anon Id"},"props":{"anyOf":[{"$ref":"#/components/schemas/JsonValue"},{"type":"null"}]}},"additionalProperties":false,"type":"object","required":["name"],"title":"AnalyticsEventIn","description":"A client-emitted funnel event. Money/status events are written by the\nserver only and never accepted here (arch §11)."},"ApiKeyInfo":{"properties":{"key_prefix":{"type":"string","title":"Key Prefix"},"label":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Label"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"last_used_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Last Used At","description":"Coarse: updated at most once a minute."},"quota":{"$ref":"#/components/schemas/ApiKeyQuota"}},"additionalProperties":false,"type":"object","required":["key_prefix","label","created_at","last_used_at","quota"],"title":"ApiKeyInfo","description":"What `GET /developers/api-keys/current` says about the presented key."},"ApiKeyIssueRequest":{"properties":{"email":{"type":"string","maxLength":254,"title":"Email","description":"Contact address the key is issued to. Not verified by mail; it is the handle the per-owner ceiling (5 active keys) is counted on.","examples":["agent@example.com"]},"label":{"anyOf":[{"type":"string","maxLength":80},{"type":"null"}],"title":"Label","description":"Free-text tag to tell your keys apart.","examples":["my-crawler"]}},"additionalProperties":false,"type":"object","required":["email"],"title":"ApiKeyIssueRequest"},"ApiKeyIssued":{"properties":{"key":{"type":"string","title":"Key","description":"The secret, `vf_live_` + 32 characters. Shown once; store it. Present it as `Authorization: Bearer <key>`.","examples":["vf_live_AbCdEfGhIjKlMnOpQrStUvWxYz012345"]},"key_prefix":{"type":"string","title":"Key Prefix","description":"The first characters of the key — how it is referred to later.","examples":["vf_live_AbCdEfGh"]},"created_at":{"type":"string","format":"date-time","title":"Created At"},"quota":{"$ref":"#/components/schemas/ApiKeyQuota"}},"additionalProperties":false,"type":"object","required":["key","key_prefix","created_at","quota"],"title":"ApiKeyIssued","description":"The issuing response — the ONLY time the secret is shown."},"ApiKeyQuota":{"properties":{"requests_per_minute":{"type":"integer","title":"Requests Per Minute","description":"Requests per 60-second window charged to this key (policy `public_read_key`) instead of to the caller's IP.","examples":[3000]}},"additionalProperties":false,"type":"object","required":["requests_per_minute"],"title":"ApiKeyQuota","description":"What the key buys on the public read surface."},"ApplicantAnswers":{"properties":{"applicant_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Applicant Id"},"idx":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Idx"},"answers":{"$ref":"#/components/schemas/JsonValue"}},"additionalProperties":false,"type":"object","required":["answers"],"title":"ApplicantAnswers","description":"A single applicant's answers within an ``applicant_group`` stage."},"ApplicantGroupStage":{"properties":{"key":{"type":"string","title":"Key"},"title":{"additionalProperties":{"type":"string"},"type":"object","title":"Title"},"visible_if":{"anyOf":[{"$ref":"#/components/schemas/JsonValue"},{"type":"null"}]},"type":{"type":"string","const":"applicant_group","title":"Type"},"min":{"type":"integer","title":"Min","default":1},"max":{"type":"integer","title":"Max","default":1},"fields":{"items":{"oneOf":[{"$ref":"#/components/schemas/ScalarFieldDescriptor"},{"$ref":"#/components/schemas/SelectFieldDescriptor"},{"$ref":"#/components/schemas/ListGroupFieldDescriptor"}],"discriminator":{"propertyName":"type","mapping":{"bool":"#/components/schemas/ScalarFieldDescriptor","country":"#/components/schemas/ScalarFieldDescriptor","date":"#/components/schemas/ScalarFieldDescriptor","email":"#/components/schemas/ScalarFieldDescriptor","file":"#/components/schemas/ScalarFieldDescriptor","gender":"#/components/schemas/ScalarFieldDescriptor","int":"#/components/schemas/ScalarFieldDescriptor","list_group":"#/components/schemas/ListGroupFieldDescriptor","multiselect":"#/components/schemas/SelectFieldDescriptor","number":"#/components/schemas/ScalarFieldDescriptor","passport":"#/components/schemas/ScalarFieldDescriptor","phone":"#/components/schemas/ScalarFieldDescriptor","select":"#/components/schemas/SelectFieldDescriptor","text":"#/components/schemas/ScalarFieldDescriptor","textarea":"#/components/schemas/ScalarFieldDescriptor"}}},"type":"array","title":"Fields"},"sections":{"anyOf":[{"items":{"$ref":"#/components/schemas/SectionDescriptor"},"type":"array"},{"type":"null"}],"title":"Sections"}},"additionalProperties":false,"type":"object","required":["key","type"],"title":"ApplicantGroupStage"},"ApplicantOutcome":{"type":"string","enum":["pending","filed","approved","rejected","refiling"],"title":"ApplicantOutcome","description":"Per-applicant outcome (arch §4 \"Per-applicant исходы\")."},"ApplicantRollup":{"properties":{"total":{"type":"integer","title":"Total"},"issued":{"type":"integer","title":"Issued"},"processing":{"type":"integer","title":"Processing"},"declined":{"type":"integer","title":"Declined"}},"additionalProperties":false,"type":"object","required":["total","issued","processing","declined"],"title":"ApplicantRollup","description":"Client-facing rollup: \"3 issued, 1 processing\" (arch §4)."},"ApplicantView":{"properties":{"id":{"type":"string","title":"Id"},"idx":{"type":"integer","title":"Idx"},"full_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Full Name"},"citizenship":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Citizenship"},"outcome":{"type":"string","enum":["processing","issued","declined"],"title":"Outcome","default":"processing"},"visa_result_document_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Visa Result Document Id"},"visa_grant_number":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Visa Grant Number"},"answers":{"$ref":"#/components/schemas/JsonValue"},"sensitive":{"items":{"$ref":"#/components/schemas/SensitiveFieldMask"},"type":"array","title":"Sensitive"},"traveller_profile_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Traveller Profile Id"}},"additionalProperties":false,"type":"object","required":["id","idx"],"title":"ApplicantView","description":"Applicant as the client sees it — no passport number, no internal outcome.\n\nCarries the applicant's own raw ``answers`` (the non-sensitive roster fields\nthe owner typed — name, birth date, citizenship, passport *dates*), so a\nresumed multi-applicant draft can re-hydrate the applicant_group form instead\nof showing empty rows (slice E4c). The plaintext ``answers`` never holds the\npassport *number* — that is ``sensitive`` and rejected from this JSONB on\nsave_stage — so exposing them back to the owning applicant leaks nothing the\noperator surface (:class:`OperatorApplicantView`) does not already carry."},"ApplicationDetail":{"properties":{"id":{"type":"string","title":"Id"},"market":{"type":"string","title":"Market"},"direction":{"type":"string","title":"Direction"},"locale":{"type":"string","title":"Locale"},"status":{"$ref":"#/components/schemas/ClientStatus"},"purchase_mode":{"anyOf":[{"$ref":"#/components/schemas/PurchaseMode"},{"type":"null"}]},"handoff_mode":{"anyOf":[{"$ref":"#/components/schemas/HandoffMode"},{"type":"null"}]},"tariff_key":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Tariff Key"},"slot_stage":{"anyOf":[{"$ref":"#/components/schemas/SlotStage"},{"type":"null"}]},"pin":{"$ref":"#/components/schemas/VersionPinView"},"answers":{"$ref":"#/components/schemas/JsonValue"},"applicants":{"items":{"$ref":"#/components/schemas/ApplicantView"},"type":"array","title":"Applicants"},"documents":{"items":{"$ref":"#/components/schemas/DocumentView"},"type":"array","title":"Documents"},"delivery":{"$ref":"#/components/schemas/DeliveryView"},"rollup":{"$ref":"#/components/schemas/ApplicantRollup"},"action_required":{"anyOf":[{"$ref":"#/components/schemas/ActionRequiredView"},{"type":"null"}]},"sensitive":{"items":{"$ref":"#/components/schemas/SensitiveFieldMask"},"type":"array","title":"Sensitive"},"field_errors":{"items":{"$ref":"#/components/schemas/ErrorDetail"},"type":"array","title":"Field Errors"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"updated_at":{"type":"string","format":"date-time","title":"Updated At"}},"additionalProperties":false,"type":"object","required":["id","market","direction","locale","status","pin","answers","rollup","created_at","updated_at"],"title":"ApplicationDetail"},"ApplicationListResponse":{"properties":{"items":{"items":{"$ref":"#/components/schemas/ApplicationSummary"},"type":"array","title":"Items"}},"additionalProperties":false,"type":"object","title":"ApplicationListResponse"},"ApplicationSummary":{"properties":{"id":{"type":"string","title":"Id"},"market":{"type":"string","title":"Market"},"direction":{"type":"string","title":"Direction"},"status":{"$ref":"#/components/schemas/ClientStatus"},"purchase_mode":{"anyOf":[{"$ref":"#/components/schemas/PurchaseMode"},{"type":"null"}]},"handoff_mode":{"anyOf":[{"$ref":"#/components/schemas/HandoffMode"},{"type":"null"}]},"tariff_key":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Tariff Key"},"rollup":{"$ref":"#/components/schemas/ApplicantRollup"},"service_payment":{"$ref":"#/components/schemas/ServicePaymentSummary"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"updated_at":{"type":"string","format":"date-time","title":"Updated At"}},"additionalProperties":false,"type":"object","required":["id","market","direction","status","rollup","created_at","updated_at"],"title":"ApplicationSummary"},"ApplyTravellerProfileRequest":{"properties":{"application_id":{"type":"string","title":"Application Id"}},"additionalProperties":false,"type":"object","required":["application_id"],"title":"ApplyTravellerProfileRequest","description":"Apply a saved traveller into one of the caller's own draft applications."},"ApplyTravellerProfileResponse":{"properties":{"applicant_id":{"type":"string","title":"Applicant Id"},"idx":{"type":"integer","title":"Idx"},"sensitive":{"items":{"$ref":"#/components/schemas/SensitiveFieldMask"},"type":"array","title":"Sensitive"}},"additionalProperties":false,"type":"object","required":["applicant_id","idx"],"title":"ApplyTravellerProfileResponse","description":"The applicant row the profile materialised into the target application.\n\n``applicant_id`` addresses the new roster row, so the client hydrates it and\nsubsequent autosaves patch it in place instead of duplicating the roster.\n``sensitive`` reports which fields were re-encrypted into the new slot (their\nmasks) — an empty list means the profile carried no ciphertext to re-seal\n(e.g. its source application was purged) and the passport must be re-entered."},"ArchiveResultView":{"properties":{"layer":{"type":"string","title":"Layer"},"target":{"type":"string","title":"Target"},"affected_versions":{"type":"integer","title":"Affected Versions"}},"additionalProperties":false,"type":"object","required":["layer","target","affected_versions"],"title":"ArchiveResultView","description":"Outcome of an archive / unarchive — how many versions were flipped."},"Attribution":{"properties":{"landing_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Landing Id"},"utm_source":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Utm Source"},"utm_medium":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Utm Medium"},"utm_campaign":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Utm Campaign"},"utm_content":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Utm Content"},"utm_term":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Utm Term"},"gclid":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Gclid"},"fbclid":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Fbclid"},"first_attribution_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"First Attribution Id"},"last_attribution_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Last Attribution Id"}},"additionalProperties":false,"type":"object","title":"Attribution","description":"First-touch attribution read from the cookie (arch §8). Never the market\nsource of truth — the market comes from the path / request body (ADR-16)."},"BusinessReviewRequest":{"properties":{"verdict":{"$ref":"#/components/schemas/BusinessReviewVerdict"},"reason":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Reason"}},"additionalProperties":false,"type":"object","required":["verdict"],"title":"BusinessReviewRequest"},"BusinessReviewVerdict":{"type":"string","enum":["accepted","request_changes"],"title":"BusinessReviewVerdict"},"BusinessReviewView":{"properties":{"id":{"type":"string","title":"Id"},"document_id":{"type":"string","title":"Document Id"},"application_id":{"type":"string","title":"Application Id"},"applicant_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Applicant Id"},"requirement_key":{"type":"string","title":"Requirement Key"},"verdict":{"$ref":"#/components/schemas/BusinessReviewVerdict"},"reason":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Reason"},"decided_by":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Decided By"},"command_id":{"type":"string","title":"Command Id"},"created_at":{"type":"string","format":"date-time","title":"Created At"}},"additionalProperties":false,"type":"object","required":["id","document_id","application_id","requirement_key","verdict","command_id","created_at"],"title":"BusinessReviewView"},"CampaignOfferResponse":{"properties":{"discount_percent":{"anyOf":[{"type":"integer","maximum":100.0,"minimum":1.0},{"type":"null"}],"title":"Discount Percent"}},"additionalProperties":false,"type":"object","title":"CampaignOfferResponse","description":"Presentation-safe current offer; never includes a code or link id."},"CampaignPromoCreate":{"properties":{"code":{"type":"string","maxLength":64,"minLength":1,"title":"Code"},"discount_percent":{"type":"integer","maximum":100.0,"minimum":1.0,"title":"Discount Percent"},"expires_at":{"type":"string","format":"date-time","title":"Expires At"},"activation_limit":{"type":"integer","maximum":1000000.0,"minimum":1.0,"title":"Activation Limit"},"is_active":{"type":"boolean","title":"Is Active","default":false},"note":{"anyOf":[{"type":"string","maxLength":500},{"type":"null"}],"title":"Note"}},"additionalProperties":false,"type":"object","required":["code","discount_percent","expires_at","activation_limit"],"title":"CampaignPromoCreate"},"CampaignPromoList":{"properties":{"items":{"items":{"$ref":"#/components/schemas/CampaignPromoView"},"type":"array","title":"Items"},"total":{"type":"integer","title":"Total"}},"additionalProperties":false,"type":"object","required":["items","total"],"title":"CampaignPromoList"},"CampaignPromoView":{"properties":{"id":{"type":"string","title":"Id"},"code":{"type":"string","title":"Code"},"discount_percent":{"type":"integer","title":"Discount Percent"},"expires_at":{"type":"string","format":"date-time","title":"Expires At"},"activation_limit":{"type":"integer","title":"Activation Limit"},"activations_used":{"type":"integer","title":"Activations Used"},"is_active":{"type":"boolean","title":"Is Active"},"note":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Note"},"created_at":{"type":"string","format":"date-time","title":"Created At"}},"additionalProperties":false,"type":"object","required":["id","code","discount_percent","expires_at","activation_limit","activations_used","is_active","created_at"],"title":"CampaignPromoView"},"CatalogEntryView":{"properties":{"layer":{"type":"string","title":"Layer"},"target":{"type":"string","title":"Target"},"published_version":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Published Version"},"status":{"type":"string","title":"Status"},"has_draft":{"type":"boolean","title":"Has Draft"}},"additionalProperties":false,"type":"object","required":["layer","target","status","has_draft"],"title":"CatalogEntryView","description":"One ``(layer, target)`` in the Config Studio catalog list."},"CatalogOverview":{"properties":{"entries":{"items":{"$ref":"#/components/schemas/CatalogEntryView"},"type":"array","title":"Entries"}},"additionalProperties":false,"type":"object","title":"CatalogOverview"},"CatalogResponse":{"properties":{"markets":{"items":{"$ref":"#/components/schemas/MarketSummary"},"type":"array","title":"Markets"}},"additionalProperties":false,"type":"object","title":"CatalogResponse"},"ChargeStatus":{"type":"string","enum":["succeeded","pending","requires_action","failed"],"title":"ChargeStatus","description":"Mirrors ``ChargeOutcome.status`` from ``PaymentProvider`` (arch §9)."},"ChecklistItemState":{"properties":{"key":{"type":"string","title":"Key"},"applicant_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Applicant Id"},"text":{"additionalProperties":{"type":"string"},"type":"object","title":"Text"},"done":{"type":"boolean","title":"Done","default":false}},"additionalProperties":false,"type":"object","required":["key"],"title":"ChecklistItemState"},"ChecklistUpdate":{"properties":{"key":{"type":"string","title":"Key"},"applicant_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Applicant Id"},"done":{"type":"boolean","title":"Done"}},"additionalProperties":false,"type":"object","required":["key","done"],"title":"ChecklistUpdate"},"CheckoutLeg":{"properties":{"kind":{"type":"string","enum":["service","gov_fee","subscription"],"title":"Kind"},"amount":{"$ref":"#/components/schemas/Money"},"status":{"$ref":"#/components/schemas/ChargeStatus"},"payment_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Payment Id"}},"additionalProperties":false,"type":"object","required":["kind","amount","status"],"title":"CheckoutLeg"},"CheckoutRequest":{"properties":{"application_id":{"type":"string","title":"Application Id"},"purchase_mode":{"$ref":"#/components/schemas/PurchaseMode"},"promo_code":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Promo Code"},"client_request_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Client Request Id"},"payer_document":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Payer Document"}},"additionalProperties":false,"type":"object","required":["application_id","purchase_mode"],"title":"CheckoutRequest"},"CheckoutResponse":{"properties":{"application_id":{"type":"string","title":"Application Id"},"order_id":{"type":"string","title":"Order Id"},"order_state":{"type":"string","title":"Order State"},"status":{"$ref":"#/components/schemas/ChargeStatus"},"payment_provider":{"type":"string","title":"Payment Provider"},"payer_document_last4":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Payer Document Last4"},"legs":{"items":{"$ref":"#/components/schemas/CheckoutLeg"},"type":"array","title":"Legs"},"next_action":{"oneOf":[{"$ref":"#/components/schemas/NoAction"},{"$ref":"#/components/schemas/ClientSecretAction"},{"$ref":"#/components/schemas/RedirectAction"}],"title":"Next Action","discriminator":{"propertyName":"type","mapping":{"client_secret":"#/components/schemas/ClientSecretAction","none":"#/components/schemas/NoAction","redirect":"#/components/schemas/RedirectAction"}}}},"additionalProperties":false,"type":"object","required":["application_id","order_id","order_state","status","payment_provider","next_action"],"title":"CheckoutResponse","description":"The checkout outcome — and the ONE place the front learns its payment path.\n\n**Why the provider identifier lives here and not in the catalog** (slice PAY4).\n``payment_provider`` is pinned on the *market* axis\n(``MarketProfile.payment_provider``), so publishing it as part of the compiled\nflow / catalog would be the obvious place. It would also be wrong: PAY1 pins an\norder that already has money in flight to the PSP written on its ``payments``\nrow, precisely so an edit of the market YAML mid-checkout cannot send the second\nleg to a different PSP. A front reading the provider off the catalog would, right\nafter such an edit, render the payment path of the NEW provider for an order the\nbackend is still finishing on the OLD one — the exact split-brain the row pin\nexists to prevent. The checkout response is the answer of the same call that\nresolved the provider for this order, so it is the only source that is true for\n*this* order rather than for the market's current declaration.\n\n**Only the identifier crosses the wire.** Nothing here is a credential: the\npublishable/public key and mode of a PSP reach the browser through the front's\nown public build config, and a secret key or webhook secret must never appear in\nan API response (CLAUDE.md §8). The front maps the identifier onto the adapter it\nalready ships.\n\nThe sibling read paths stay unchanged on purpose — ``GET\n…/applications/{id}/payments`` is the *state* poll of an order whose path was\nalready chosen from this response; duplicating the provider there would create a\nsecond, staler source of truth for the same fact."},"ClaimResult":{"properties":{"application_id":{"type":"string","title":"Application Id"},"assigned_to":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Assigned To"},"claimed":{"type":"boolean","title":"Claimed"}},"additionalProperties":false,"type":"object","required":["application_id","claimed"],"title":"ClaimResult","description":"Outcome of the claim CAS on ``applications.assigned_to`` (arch §7)."},"ClientSecretAction":{"properties":{"type":{"type":"string","const":"client_secret","title":"Type"},"client_secret":{"type":"string","title":"Client Secret"},"payment_method_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Payment Method Id"}},"additionalProperties":false,"type":"object","required":["type","client_secret"],"title":"ClientSecretAction","description":"3DS / SetupIntent confirmation handled by the provider SDK on the front."},"ClientStatus":{"type":"string","enum":["draft","submitted","in_review","action_required","processing","issued","declined","refunded","cancelled"],"title":"ClientStatus","description":"The **only** status vocabulary the applicant ЛК ever sees (arch §4).\n\nA pure ``internal → client`` projection lives server-side and is pinned\nhere; ``REFILING``/``FILED``/``READY_TO_FILE`` etc. collapse into\n``processing`` so the client never watches the ``REJECTED→FILING`` refiling\nsee-saw."},"ConfigProblemView":{"properties":{"path":{"type":"string","title":"Path"},"message":{"type":"string","title":"Message"}},"additionalProperties":false,"type":"object","required":["path","message"],"title":"ConfigProblemView"},"ConfirmRequest":{"properties":{"document_id":{"type":"string","title":"Document Id"},"outcome":{"anyOf":[{"type":"string","enum":["approved","rejected"]},{"type":"null"}],"title":"Outcome"},"reject_reason":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Reject Reason"}},"additionalProperties":false,"type":"object","required":["document_id"],"title":"ConfirmRequest"},"ConsentAck":{"properties":{"kind":{"$ref":"#/components/schemas/ConsentKind"},"text_version":{"type":"string","title":"Text Version"},"applicant_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Applicant Id"}},"additionalProperties":false,"type":"object","required":["kind","text_version"],"title":"ConsentAck"},"ConsentDescriptor":{"properties":{"kind":{"$ref":"#/components/schemas/ConsentKind"},"text_version":{"type":"string","title":"Text Version"},"label":{"additionalProperties":{"type":"string"},"type":"object","title":"Label"},"required":{"type":"boolean","title":"Required","default":true}},"additionalProperties":false,"type":"object","required":["kind","text_version"],"title":"ConsentDescriptor"},"ConsentKind":{"type":"string","enum":["aydinlatma_ack","dpdp_notice_ack","acik_riza_marketing","guardian_declaration","early_performance_15_1h","on_bilgilendirme_ack","scan_ocr_processing"],"title":"ConsentKind","description":"Provable consent kinds recorded in ``consent_records`` (arch §4, §10)."},"CreateApplicationRequest":{"properties":{"market":{"type":"string","title":"Market"},"direction":{"type":"string","title":"Direction"},"locale":{"type":"string","title":"Locale"},"client_request_id":{"type":"string","maxLength":128,"minLength":1,"title":"Client Request Id"},"applicant_count":{"anyOf":[{"type":"integer","minimum":1.0},{"type":"null"}],"title":"Applicant Count"},"purchase_mode":{"anyOf":[{"$ref":"#/components/schemas/PurchaseMode"},{"type":"null"}]},"attribution":{"anyOf":[{"$ref":"#/components/schemas/Attribution"},{"type":"null"}]}},"additionalProperties":false,"type":"object","required":["market","direction","locale","client_request_id"],"title":"CreateApplicationRequest"},"DeadLetterList":{"properties":{"items":{"items":{"$ref":"#/components/schemas/DeadLetterView"},"type":"array","title":"Items"},"total":{"type":"integer","title":"Total"},"limit":{"type":"integer","title":"Limit"},"offset":{"type":"integer","title":"Offset"}},"additionalProperties":false,"type":"object","required":["items","total","limit","offset"],"title":"DeadLetterList","description":"A page of dead-lettered rows plus the full count (the CRM badge)."},"DeadLetterView":{"properties":{"id":{"type":"integer","title":"Id"},"aggregate_type":{"type":"string","title":"Aggregate Type"},"aggregate_id":{"type":"string","title":"Aggregate Id"},"event_type":{"type":"string","title":"Event Type"},"attempts":{"type":"integer","title":"Attempts"},"last_error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Last Error"},"dead_lettered_at":{"type":"string","format":"date-time","title":"Dead Lettered At"},"available_at":{"type":"string","format":"date-time","title":"Available At"},"created_at":{"type":"string","format":"date-time","title":"Created At"}},"additionalProperties":false,"type":"object","required":["id","aggregate_type","aggregate_id","event_type","attempts","dead_lettered_at","available_at","created_at"],"title":"DeadLetterView","description":"One dead-lettered outbox row (routing/correlation ids + error only)."},"DeliverableStateView":{"properties":{"key":{"type":"string","title":"Key"},"type":{"type":"string","title":"Type"},"per_applicant":{"type":"boolean","title":"Per Applicant"},"applicant_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Applicant Id"},"required":{"type":"boolean","title":"Required","default":false},"applies_to":{"type":"string","title":"Applies To","default":"all"},"visible_to_client":{"type":"boolean","title":"Visible To Client","default":true},"carries_outcome":{"type":"boolean","title":"Carries Outcome","default":false},"writable":{"type":"boolean","title":"Writable","default":false},"label":{"additionalProperties":{"type":"string"},"type":"object","title":"Label"},"help":{"additionalProperties":{"type":"string"},"type":"object","title":"Help"},"text":{"additionalProperties":{"type":"string"},"type":"object","title":"Text"},"url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Url"},"options":{"items":{"$ref":"#/components/schemas/OptionDescriptor"},"type":"array","title":"Options"},"accept":{"items":{"type":"string"},"type":"array","title":"Accept"},"max_mb":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Max Mb"},"order":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Order"},"value":{"$ref":"#/components/schemas/JsonValue"},"document_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Document Id"},"document_status":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Document Status"}},"additionalProperties":false,"type":"object","required":["key","type","per_applicant"],"title":"DeliverableStateView","description":"One delivery item of the case, as the operator's form needs it (DLV3).\n\nDescriptor (from the PINNED direction config) + the current state, so the CRM\ncan render the выдача form without re-reading the config: the scalar ``value``\nfor a writable item, the uploaded ``document_id``/``document_status`` for a\n``file`` one. This is the OPERATOR projection — it deliberately carries the\noperator-only items (``visible_to_client=false``) too; the client-facing\nprojection is a separate, filtered view (slice DLV4)."},"DeliverablesPatch":{"properties":{"values":{"additionalProperties":{"$ref":"#/components/schemas/JsonValue"},"type":"object","title":"Values"}},"additionalProperties":false,"type":"object","title":"DeliverablesPatch","description":"Operator write into the case's выдача (slice DLV3).\n\nMerge semantics, mirroring the intake autosave: only the keys present in\n``values`` are written, an absent key keeps whatever is stored, and an\nexplicit ``null`` CLEARS its key. Which keys are addressable depends on the\nendpoint — the per-applicant PATCH takes the direction's ``per_applicant``\nitems, the case-level one takes the rest; a key from the other scope (or from\nno scope at all) is a 422, never a silent write."},"DeliveryView":{"properties":{"items":{"items":{"oneOf":[{"$ref":"#/components/schemas/ScalarDeliverable"},{"$ref":"#/components/schemas/SelectDeliverable"},{"$ref":"#/components/schemas/FileDeliverable"},{"$ref":"#/components/schemas/NoteDeliverable"},{"$ref":"#/components/schemas/LinkDeliverable"}],"discriminator":{"propertyName":"type","mapping":{"bool":"#/components/schemas/ScalarDeliverable","country":"#/components/schemas/ScalarDeliverable","date":"#/components/schemas/ScalarDeliverable","email":"#/components/schemas/ScalarDeliverable","file":"#/components/schemas/FileDeliverable","int":"#/components/schemas/ScalarDeliverable","link":"#/components/schemas/LinkDeliverable","multiselect":"#/components/schemas/SelectDeliverable","note":"#/components/schemas/NoteDeliverable","number":"#/components/schemas/ScalarDeliverable","phone":"#/components/schemas/ScalarDeliverable","select":"#/components/schemas/SelectDeliverable","text":"#/components/schemas/ScalarDeliverable","textarea":"#/components/schemas/ScalarDeliverable"}}},"type":"array","title":"Items"}},"additionalProperties":false,"type":"object","title":"DeliveryView","description":"What this case handed back, in the direction's authored order.\n\n``items`` is EMPTY (never ``null``) for a direction that issues nothing and\nfor a case that has not been delivered yet — the front renders «nothing yet»,\nit never has to distinguish two flavours of absence."},"DirectionFaqDescriptor":{"properties":{"key":{"type":"string","enum":["price","processing_time","documents","entries"],"title":"Key"},"question":{"additionalProperties":{"type":"string"},"type":"object","title":"Question"},"answer":{"additionalProperties":{"type":"string"},"type":"object","title":"Answer"}},"additionalProperties":false,"type":"object","required":["key"],"title":"DirectionFaqDescriptor"},"DirectionSourceDescriptor":{"properties":{"key":{"type":"string","title":"Key"},"label":{"additionalProperties":{"type":"string"},"type":"object","title":"Label"},"url":{"type":"string","title":"Url"},"checked_at":{"type":"string","format":"date","title":"Checked At"}},"additionalProperties":false,"type":"object","required":["key","url","checked_at"],"title":"DirectionSourceDescriptor"},"DirectionSummary":{"properties":{"slug":{"type":"string","title":"Slug"},"destination":{"type":"string","title":"Destination"},"visa_kind":{"type":"string","title":"Visa Kind"},"title":{"additionalProperties":{"type":"string"},"type":"object","title":"Title"},"price":{"anyOf":[{"$ref":"#/components/schemas/Money"},{"type":"null"}]}},"additionalProperties":false,"type":"object","required":["slug","destination","visa_kind"],"title":"DirectionSummary"},"DocumentRequirementDescriptor":{"properties":{"key":{"type":"string","title":"Key"},"label":{"additionalProperties":{"type":"string"},"type":"object","title":"Label"},"per_applicant":{"type":"boolean","title":"Per Applicant","default":false},"max_mb":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Max Mb"},"accept":{"items":{"type":"string"},"type":"array","title":"Accept"},"ocr_hook":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Ocr Hook"},"required_if":{"anyOf":[{"$ref":"#/components/schemas/JsonValue"},{"type":"null"}]},"scan":{"anyOf":[{"type":"string","enum":["off","optional","required"]},{"type":"null"}],"title":"Scan"}},"additionalProperties":false,"type":"object","required":["key"],"title":"DocumentRequirementDescriptor"},"DocumentStatus":{"type":"string","enum":["pending","uploaded","verified","rejected"],"title":"DocumentStatus"},"DocumentView":{"properties":{"id":{"type":"string","title":"Id"},"application_id":{"type":"string","title":"Application Id"},"applicant_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Applicant Id"},"requirement_key":{"type":"string","title":"Requirement Key"},"status":{"$ref":"#/components/schemas/DocumentStatus"},"mime":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Mime"},"size_bytes":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Size Bytes"},"reject_reason":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Reject Reason"},"ocr_result":{"anyOf":[{"$ref":"#/components/schemas/OcrResult"},{"type":"null"}]},"ocr_recognized":{"anyOf":[{"$ref":"#/components/schemas/OcrRecognized"},{"type":"null"}]},"uploaded_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Uploaded At"}},"additionalProperties":false,"type":"object","required":["id","application_id","requirement_key","status"],"title":"DocumentView"},"DocumentsStage":{"properties":{"key":{"type":"string","title":"Key"},"title":{"additionalProperties":{"type":"string"},"type":"object","title":"Title"},"visible_if":{"anyOf":[{"$ref":"#/components/schemas/JsonValue"},{"type":"null"}]},"type":{"type":"string","const":"documents","title":"Type"},"requirements":{"items":{"$ref":"#/components/schemas/DocumentRequirementDescriptor"},"type":"array","title":"Requirements"}},"additionalProperties":false,"type":"object","required":["key","type"],"title":"DocumentsStage"},"DossierGenerationView":{"properties":{"generation_id":{"type":"string","title":"Generation Id"},"document_ids":{"items":{"type":"string"},"type":"array","title":"Document Ids"},"replayed":{"type":"boolean","title":"Replayed"}},"additionalProperties":false,"type":"object","required":["generation_id","document_ids","replayed"],"title":"DossierGenerationView"},"DossierPackageView":{"properties":{"generation_id":{"type":"string","title":"Generation Id"},"template_version":{"type":"string","title":"Template Version"},"input_hash":{"type":"string","title":"Input Hash"},"document_ids":{"items":{"type":"string"},"type":"array","title":"Document Ids"},"ready_for_approval":{"type":"boolean","title":"Ready For Approval","default":false},"blockers":{"items":{"type":"string"},"type":"array","title":"Blockers"}},"additionalProperties":false,"type":"object","required":["generation_id","template_version","input_hash"],"title":"DossierPackageView"},"DownloadResponse":{"properties":{"url":{"type":"string","title":"Url"},"expires_at":{"type":"string","format":"date-time","title":"Expires At"}},"additionalProperties":false,"type":"object","required":["url","expires_at"],"title":"DownloadResponse"},"DraftCreate":{"properties":{"payload":{"additionalProperties":true,"type":"object","title":"Payload"},"base_version":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Base Version"}},"additionalProperties":false,"type":"object","title":"DraftCreate","description":"Open the draft for a target (its raw config payload, unvalidated here)."},"DraftRebaseRequest":{"properties":{"new_base_version":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"New Base Version"}},"additionalProperties":false,"type":"object","title":"DraftRebaseRequest","description":"Re-fork the active draft onto the current published version (slice CFG14c).\n\nAfter a publish conflict the editor's draft still pins the version it forked\nfrom, so a re-publish 409s again. Rebasing moves the pin onto the version that\nlanded under the editor WITHOUT touching the payload — the in-flight edits are\nkept, and the next publish's optimistic-lock check passes."},"DraftUpdate":{"properties":{"payload":{"additionalProperties":true,"type":"object","title":"Payload"},"expected_base_version":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Expected Base Version"}},"additionalProperties":false,"type":"object","title":"DraftUpdate","description":"Re-save the draft, guarding on the ``base_version`` last read."},"DraftView":{"properties":{"id":{"type":"string","title":"Id"},"layer":{"type":"string","title":"Layer"},"target":{"type":"string","title":"Target"},"author_id":{"type":"string","title":"Author Id"},"base_version":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Base Version"},"payload":{"additionalProperties":true,"type":"object","title":"Payload"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"updated_at":{"type":"string","format":"date-time","title":"Updated At"}},"additionalProperties":false,"type":"object","required":["id","layer","target","author_id","created_at","updated_at"],"title":"DraftView","description":"The single active draft for a ``(layer, target)`` pair."},"EmbedAttribution":{"properties":{"page_url":{"type":"string","maxLength":2048,"title":"Page Url"},"utm_source":{"anyOf":[{"type":"string","maxLength":256},{"type":"null"}],"title":"Utm Source"},"utm_medium":{"anyOf":[{"type":"string","maxLength":256},{"type":"null"}],"title":"Utm Medium"},"utm_campaign":{"anyOf":[{"type":"string","maxLength":256},{"type":"null"}],"title":"Utm Campaign"},"utm_content":{"anyOf":[{"type":"string","maxLength":256},{"type":"null"}],"title":"Utm Content"},"utm_term":{"anyOf":[{"type":"string","maxLength":256},{"type":"null"}],"title":"Utm Term"}},"additionalProperties":false,"type":"object","required":["page_url"],"title":"EmbedAttribution","description":"Where the widget was rendered, in a CLOSED vocabulary.\n\nOnly the host page URL and the five standard UTM keys — no free-form bag. The\nwidget runs on someone else's page, so whatever this carries is attacker\n-controlled text that we store; a closed shape is what keeps \"attribution\"\nfrom becoming a side-channel for arbitrary payloads (or smuggled PII)."},"EmbedClaimRequest":{"properties":{"token":{"type":"string","title":"Token"}},"additionalProperties":false,"type":"object","required":["token"],"title":"EmbedClaimRequest","description":"The token being redeemed — and *nothing else about it* (slice EMB2b).\n\n``token`` carries **no** ``min_length`` / ``max_length`` / pattern on purpose.\nEvery constraint here would be a distinguishable refusal: a 422 from the schema\nfor an empty or truncated value versus the single opaque 404 the server answers\nfor an unknown, burned or expired one. That difference is an oracle for the\ntoken's shape and for which strings are worth trying, so validation of the value\nis left entirely to the hash lookup, where every dead token dies the same death\n(:mod:`trippy_core.embed.claim`).\n\nThe one refusal that *is* distinguishable — and must be — is omitting the field\naltogether: that is a malformed request, not a wrong token."},"EmbedClaimResponse":{"properties":{"market":{"type":"string","title":"Market"},"direction":{"type":"string","title":"Direction"},"answers":{"additionalProperties":{"$ref":"#/components/schemas/JsonValue"},"type":"object","title":"Answers"},"attribution":{"$ref":"#/components/schemas/EmbedAttribution"}},"additionalProperties":false,"type":"object","required":["market","direction","answers","attribution"],"title":"EmbedClaimResponse","description":"What a redeemed handoff hands to the signed-in visitor's session.\n\nVerbatim, not recomputed: the pair and the answers are what the widget stored\nat handoff time, never re-checked against today's publication (an intervening\nconfig change must not swallow data the visitor already gave us — the live flow\nfilters at apply time)."},"EmbedConsentAck":{"properties":{"kind":{"$ref":"#/components/schemas/ConsentKind"},"text_version":{"type":"string","title":"Text Version"},"accepted":{"type":"boolean","title":"Accepted"}},"additionalProperties":false,"type":"object","required":["kind","text_version","accepted"],"title":"EmbedConsentAck","description":"The acknowledgement the widget displayed, as a FACT — never as wording.\n\n``kind`` and ``text_version`` are echoed back from the description the widget\nrendered off, so the server can tell a widget rendered off the *current*\npublication from one rendered off a stale copy (the fence). There is\ndeliberately no field for the text or its hash."},"EmbedHandoffRequest":{"properties":{"market":{"type":"string","title":"Market"},"direction":{"type":"string","title":"Direction"},"locale":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Locale"},"answers":{"additionalProperties":{"$ref":"#/components/schemas/JsonValue"},"type":"object","title":"Answers"},"attribution":{"$ref":"#/components/schemas/EmbedAttribution"},"consent":{"$ref":"#/components/schemas/EmbedConsentAck"}},"additionalProperties":false,"type":"object","required":["market","direction","answers","attribution","consent"],"title":"EmbedHandoffRequest","description":"One embed submission: the pair, the answers, the page, the consent."},"EmbedHandoffResponse":{"properties":{"token":{"type":"string","title":"Token"},"apply_url":{"type":"string","title":"Apply Url"}},"additionalProperties":false,"type":"object","required":["token","apply_url"],"title":"EmbedHandoffResponse","description":"What an accepted handoff hands back — exactly once."},"ErrorDetail":{"properties":{"code":{"type":"string","title":"Code"},"message":{"type":"string","title":"Message"},"field":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Field"}},"additionalProperties":false,"type":"object","required":["code","message"],"title":"ErrorDetail"},"ExpressStartRequest":{"properties":{"market":{"type":"string","maxLength":16,"minLength":2,"title":"Market"},"direction":{"type":"string","maxLength":64,"minLength":2,"title":"Direction"},"locale":{"type":"string","maxLength":8,"minLength":2,"title":"Locale"},"applicant_count":{"anyOf":[{"type":"integer","maximum":10.0,"minimum":1.0},{"type":"null"}],"title":"Applicant Count"},"email":{"type":"string","maxLength":254,"minLength":5,"pattern":"^[^@\\s]+@[^@\\s]+\\.[^@\\s]+$","title":"Email"},"consents_accepted":{"type":"boolean","title":"Consents Accepted"},"attribution":{"anyOf":[{"$ref":"#/components/schemas/Attribution"},{"type":"null"}]}},"additionalProperties":false,"type":"object","required":["market","direction","locale","email","consents_accepted"],"title":"ExpressStartRequest","description":"The public express start body.\n\nEvery field here is attacker-controlled (the surface is anonymous), so the\nbounds are part of the contract rather than a nicety — the same posture as the\npublic support form (SUP7), whose shape-check pattern the address reuses instead\nof pulling in an e-mail-validation dependency the repo does not carry."},"ExpressStartResponse":{"properties":{"express_id":{"type":"string","title":"Express Id"},"application_id":{"type":"string","title":"Application Id"},"order_id":{"type":"string","title":"Order Id"},"requires_login":{"type":"boolean","title":"Requires Login"},"next_action":{"oneOf":[{"$ref":"#/components/schemas/NoAction"},{"$ref":"#/components/schemas/ClientSecretAction"},{"$ref":"#/components/schemas/RedirectAction"}],"title":"Next Action","discriminator":{"propertyName":"type","mapping":{"client_secret":"#/components/schemas/ClientSecretAction","none":"#/components/schemas/NoAction","redirect":"#/components/schemas/RedirectAction"}}},"amount":{"$ref":"#/components/schemas/Money"}},"additionalProperties":false,"type":"object","required":["express_id","application_id","order_id","requires_login","next_action","amount"],"title":"ExpressStartResponse","description":"What the express page needs to take the payment and then poll the outcome."},"ExpressStatusResponse":{"properties":{"state":{"type":"string","title":"State"},"application_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Application Id"}},"additionalProperties":false,"type":"object","required":["state"],"title":"ExpressStatusResponse","description":"What the express page polls for.\n\nDeliberately two fields. Everything else the page might want (the order, the\ne-mail, whether the account is fresh) would be a detail about somebody else's\npurchase to a caller who cannot prove the start is his, and the refusal branch\nmust be indistinguishable from \"no such start\" — so the shape carries the state\nword plus, only on the branch that just authenticated the caller, the draft to\nwalk into."},"ExternalFeeDescriptor":{"properties":{"key":{"type":"string","title":"Key"},"kind":{"type":"string","enum":["consular","provider","other"],"title":"Kind"},"per":{"type":"string","enum":["applicant","application"],"title":"Per"},"amounts":{"items":{"$ref":"#/components/schemas/Money"},"type":"array","title":"Amounts"},"label":{"additionalProperties":{"type":"string"},"type":"object","title":"Label"},"note":{"anyOf":[{"additionalProperties":{"type":"string"},"type":"object"},{"type":"null"}],"title":"Note"},"source_url":{"type":"string","title":"Source Url"},"checked_at":{"type":"string","format":"date","title":"Checked At"}},"additionalProperties":false,"type":"object","required":["key","kind","per","source_url","checked_at"],"title":"ExternalFeeDescriptor"},"FileDeliverable":{"properties":{"key":{"type":"string","title":"Key"},"label":{"type":"string","title":"Label","default":""},"help":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Help"},"per_applicant":{"type":"boolean","title":"Per Applicant","default":false},"applicant_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Applicant Id"},"type":{"type":"string","const":"file","title":"Type"},"document_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Document Id"}},"additionalProperties":false,"type":"object","required":["key","type"],"title":"FileDeliverable","description":"An issued artifact. ``document_id`` is ``null`` until it is verified."},"FlowDescription":{"properties":{"market":{"type":"string","title":"Market"},"direction":{"type":"string","title":"Direction"},"title":{"additionalProperties":{"type":"string"},"type":"object","title":"Title"},"locales":{"items":{"type":"string"},"type":"array","title":"Locales"},"default_locale":{"type":"string","title":"Default Locale"},"currency":{"type":"string","title":"Currency"},"pricing_base_currency":{"type":"string","title":"Pricing Base Currency","default":"USD"},"presentment_currencies":{"items":{"type":"string"},"type":"array","title":"Presentment Currencies"},"eligible_passports":{"items":{"type":"string"},"type":"array","title":"Eligible Passports"},"destination":{"type":"string","title":"Destination"},"visa_kind":{"type":"string","title":"Visa Kind"},"account_model":{"$ref":"#/components/schemas/AccountModel"},"print_required":{"type":"boolean","title":"Print Required","default":false},"status_check":{"anyOf":[{"$ref":"#/components/schemas/StatusCheckView"},{"type":"null"}]},"operator_submission_mode":{"anyOf":[{"type":"string","enum":["third_party_allowed","per_applicant_only","org_account_required"]},{"type":"null"}],"title":"Operator Submission Mode"},"handoff_mode":{"anyOf":[{"type":"string","const":"self_file_dossier"},{"type":"null"}],"title":"Handoff Mode"},"external_fees":{"items":{"$ref":"#/components/schemas/ExternalFeeDescriptor"},"type":"array","title":"External Fees"},"visa_profile":{"anyOf":[{"$ref":"#/components/schemas/VisaProfileDescriptor"},{"type":"null"}]},"sources":{"items":{"$ref":"#/components/schemas/DirectionSourceDescriptor"},"type":"array","title":"Sources"},"faq":{"items":{"$ref":"#/components/schemas/DirectionFaqDescriptor"},"type":"array","title":"Faq"},"pin":{"$ref":"#/components/schemas/VersionPinView"},"stages":{"items":{"oneOf":[{"$ref":"#/components/schemas/FormStage"},{"$ref":"#/components/schemas/ApplicantGroupStage"},{"$ref":"#/components/schemas/DocumentsStage"},{"$ref":"#/components/schemas/ReviewStage"},{"$ref":"#/components/schemas/TariffStage"},{"$ref":"#/components/schemas/PaymentStage"}],"discriminator":{"propertyName":"type","mapping":{"applicant_group":"#/components/schemas/ApplicantGroupStage","documents":"#/components/schemas/DocumentsStage","form":"#/components/schemas/FormStage","payment":"#/components/schemas/PaymentStage","review":"#/components/schemas/ReviewStage","tariff":"#/components/schemas/TariffStage"}}},"type":"array","title":"Stages"},"consents":{"items":{"$ref":"#/components/schemas/ConsentDescriptor"},"type":"array","title":"Consents"},"pricing":{"$ref":"#/components/schemas/PricingDescriptor"}},"additionalProperties":false,"type":"object","required":["market","direction","locales","default_locale","currency","eligible_passports","destination","visa_kind","account_model","pin","stages","pricing"],"title":"FlowDescription","description":"The full describe-JSON payload the ``<StageRenderer>`` consumes."},"FlowDiffView":{"properties":{"market_code":{"type":"string","title":"Market Code"},"direction_slug":{"type":"string","title":"Direction Slug"},"added":{"type":"boolean","title":"Added"},"changes":{"items":{"additionalProperties":true,"type":"object"},"type":"array","title":"Changes"}},"additionalProperties":false,"type":"object","required":["market_code","direction_slug","added"],"title":"FlowDiffView","description":"Structural diff of one pair's compiled flow, draft-applied vs published."},"FormStage":{"properties":{"key":{"type":"string","title":"Key"},"title":{"additionalProperties":{"type":"string"},"type":"object","title":"Title"},"visible_if":{"anyOf":[{"$ref":"#/components/schemas/JsonValue"},{"type":"null"}]},"type":{"type":"string","const":"form","title":"Type"},"fields":{"items":{"oneOf":[{"$ref":"#/components/schemas/ScalarFieldDescriptor"},{"$ref":"#/components/schemas/SelectFieldDescriptor"},{"$ref":"#/components/schemas/ListGroupFieldDescriptor"}],"discriminator":{"propertyName":"type","mapping":{"bool":"#/components/schemas/ScalarFieldDescriptor","country":"#/components/schemas/ScalarFieldDescriptor","date":"#/components/schemas/ScalarFieldDescriptor","email":"#/components/schemas/ScalarFieldDescriptor","file":"#/components/schemas/ScalarFieldDescriptor","gender":"#/components/schemas/ScalarFieldDescriptor","int":"#/components/schemas/ScalarFieldDescriptor","list_group":"#/components/schemas/ListGroupFieldDescriptor","multiselect":"#/components/schemas/SelectFieldDescriptor","number":"#/components/schemas/ScalarFieldDescriptor","passport":"#/components/schemas/ScalarFieldDescriptor","phone":"#/components/schemas/ScalarFieldDescriptor","select":"#/components/schemas/SelectFieldDescriptor","text":"#/components/schemas/ScalarFieldDescriptor","textarea":"#/components/schemas/ScalarFieldDescriptor"}}},"type":"array","title":"Fields"},"sections":{"anyOf":[{"items":{"$ref":"#/components/schemas/SectionDescriptor"},"type":"array"},{"type":"null"}],"title":"Sections"}},"additionalProperties":false,"type":"object","required":["key","type"],"title":"FormStage"},"GovFeeDescriptor":{"properties":{"by_passport":{"additionalProperties":{"items":{"$ref":"#/components/schemas/Money"},"type":"array"},"type":"object","title":"By Passport"}},"additionalProperties":false,"type":"object","title":"GovFeeDescriptor"},"GuestThreadLinkConfirm":{"properties":{"thread_id":{"type":"string","maxLength":64,"minLength":1,"title":"Thread Id"}},"additionalProperties":false,"type":"object","required":["thread_id"],"title":"GuestThreadLinkConfirm","description":"Step 2: the explicit confirmation, naming the thread the preview showed.\n\n``thread_id`` is what makes the step explicit — a bare \"link whatever my cookie\nholds\" POST would be the automatic version this design exists to refuse, and it\nwould also silently attach a *different* thread than the one the user looked at\nif the cookie changed between the two calls."},"GuestThreadLinkPreview":{"properties":{"thread_id":{"type":"string","title":"Thread Id"},"status":{"type":"string","title":"Status"},"subject":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Subject"},"market_code":{"type":"string","title":"Market Code"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"last_message_at":{"type":"string","format":"date-time","title":"Last Message At"},"excerpt":{"type":"string","title":"Excerpt","default":""}},"additionalProperties":false,"type":"object","required":["thread_id","status","market_code","created_at","last_message_at"],"title":"GuestThreadLinkPreview","description":"What the account is about to adopt — step 1 of the guest link (slice SUP12).\n\nA *preview*, not an action: the browser's guest cookie may have been planted by\nsomebody else (cookie-tossing, review finding M7), so the user is shown the\nconversation and asked whether it is his. Hence ``excerpt`` — a subject line\nalone is too little to recognise one's own message by, and the whole transcript\nwould turn a tossed cookie into a delivery channel for attacker-written text."},"GuestThreadLinkResult":{"properties":{"thread_id":{"type":"string","title":"Thread Id"},"linked":{"type":"boolean","title":"Linked","default":false},"already_linked":{"type":"boolean","title":"Already Linked","default":false}},"additionalProperties":false,"type":"object","required":["thread_id"],"title":"GuestThreadLinkResult","description":"Outcome of the CAS on ``support_threads.user_id``.\n\n``already_linked=True`` is the idempotent replay: this thread was already the\ncaller's, so nothing changed and that is a success, not a conflict."},"HTTPValidationError":{"properties":{"detail":{"items":{"$ref":"#/components/schemas/ValidationError"},"type":"array","title":"Detail"}},"type":"object","title":"HTTPValidationError"},"HandoffMode":{"type":"string","enum":["self_file_dossier"],"title":"HandoffMode","description":"What Trippy hands off at the end of the configured processing path."},"InfoRequestCreate":{"properties":{"comment":{"type":"string","title":"Comment"},"items":{"items":{"$ref":"#/components/schemas/InfoRequestItemInput"},"type":"array","title":"Items"}},"additionalProperties":false,"type":"object","required":["comment"],"title":"InfoRequestCreate"},"InfoRequestItemInput":{"properties":{"applicant_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Applicant Id"},"kind":{"type":"string","enum":["field","document"],"title":"Kind"},"key":{"type":"string","title":"Key"},"note":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Note"}},"additionalProperties":false,"type":"object","required":["kind","key"],"title":"InfoRequestItemInput"},"InfoRequestItemView":{"properties":{"applicant_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Applicant Id"},"kind":{"type":"string","enum":["field","document"],"title":"Kind"},"key":{"type":"string","title":"Key"},"note":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Note"}},"additionalProperties":false,"type":"object","required":["kind","key"],"title":"InfoRequestItemView"},"InfoRequestResponseAttachmentView":{"properties":{"document_id":{"type":"string","title":"Document Id"},"mime":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Mime"},"size_bytes":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Size Bytes"}},"additionalProperties":false,"type":"object","required":["document_id"],"title":"InfoRequestResponseAttachmentView","description":"One attachment on an applicant's info-request answer (slice U20).\n\nA ``documents`` row (``requirement_key == \"info_response\"``) linked to the\nreply. Carries only the id + non-sensitive shape; the operator downloads the\nbytes through the ordinary ownership-checked document route (§10)."},"InfoRequestResponseCreate":{"properties":{"body":{"type":"string","title":"Body","default":""},"document_ids":{"items":{"type":"string"},"type":"array","title":"Document Ids"}},"additionalProperties":false,"type":"object","title":"InfoRequestResponseCreate","description":"The applicant's answer to one open ``info_request`` (slice U20).\n\n``body`` is the free text back to the operator; ``document_ids`` are the ids of\nalready-uploaded ``info_response`` documents (presign → PUT → confirm, the D1\npattern) to attach. At least one of the two must be non-empty (enforced in the\nrouter). The reply is accepted only while the case is NEEDS_INFO."},"InfoRequestResponseView":{"properties":{"id":{"type":"string","title":"Id"},"info_request_id":{"type":"string","title":"Info Request Id"},"body":{"type":"string","title":"Body"},"attachments":{"items":{"$ref":"#/components/schemas/InfoRequestResponseAttachmentView"},"type":"array","title":"Attachments"},"created_at":{"type":"string","format":"date-time","title":"Created At"}},"additionalProperties":false,"type":"object","required":["id","info_request_id","body","created_at"],"title":"InfoRequestResponseView","description":"An applicant's direct reply to an operator ``info_request`` (slice U20).\n\nFree ``body`` text plus its uploaded attachments. Read by the operator on the\nCRM case card (the reply ledger under each request) and echoed back to the\nowner's ЛК so they see what they already sent."},"InfoRequestView":{"properties":{"id":{"type":"string","title":"Id"},"comment":{"type":"string","title":"Comment"},"status":{"type":"string","enum":["open","resolved","cancelled"],"title":"Status"},"items":{"items":{"$ref":"#/components/schemas/InfoRequestItemView"},"type":"array","title":"Items"},"responses":{"items":{"$ref":"#/components/schemas/InfoRequestResponseView"},"type":"array","title":"Responses"},"origin_status":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Origin Status"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"resolved_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Resolved At"}},"additionalProperties":false,"type":"object","required":["id","comment","status","created_at"],"title":"InfoRequestView"},"InternalStatus":{"type":"string","enum":["DRAFT","SUBMITTED","IN_REVIEW","NEEDS_INFO","READY_TO_FILE","FILING","FILED","APPROVED","DELIVERED","REJECTED","REFILING","REFUNDED","CLOSED","CANCELLED","EXPIRED","SLOT_HUNTING","SLOT_BOOKED","ITINERARY_BOOKED","SLOT_FAILED"],"title":"InternalStatus","description":"The in-code FSM status set (arch §4). Exposed only to the operator CRM."},"JsonValue":{},"LandingConfigResponse":{"properties":{"market":{"type":"string","title":"Market"},"locale":{"type":"string","title":"Locale"},"variant":{"type":"string","title":"Variant"},"kind":{"$ref":"#/components/schemas/LandingKind"},"config":{"$ref":"#/components/schemas/JsonValue"},"pixels":{"items":{"type":"string"},"type":"array","title":"Pixels"},"bundle_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Bundle Url"}},"additionalProperties":false,"type":"object","required":["market","locale","variant","kind","config"],"title":"LandingConfigResponse","description":"Data the Next.js template renders (arch §8 \"Дефолтный\")."},"LandingKind":{"type":"string","enum":["template","custom"],"title":"LandingKind"},"LayerSchemaView":{"properties":{"layer":{"type":"string","title":"Layer"},"json_schema":{"additionalProperties":true,"type":"object","title":"Json Schema"}},"additionalProperties":false,"type":"object","required":["layer"],"title":"LayerSchemaView","description":"The JSON Schema of one config layer's engine model (slice CFG9b).\n\n``json_schema`` is the *live* ``model_json_schema()`` of the config-engine\nmodel that governs the layer — :class:`DirectionConfig` for ``direction``,\n:class:`MarketProfile` for ``market``, :class:`Overlay` for ``overlay`` — NOT a\nhand-kept duplicate. The Config Studio raw [YAML/JSON] editor (CFG9a) feeds it\nto a CodeMirror autocomplete source so key/enum suggestions track the engine\nschema automatically: change a model field, regenerate, and the editor's\ncompletions follow. Opaque to the wire (``dict``), so it never widens the\nfrozen contract when the underlying model gains a field."},"LinkDeliverable":{"properties":{"key":{"type":"string","title":"Key"},"label":{"type":"string","title":"Label","default":""},"help":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Help"},"per_applicant":{"type":"boolean","title":"Per Applicant","default":false},"applicant_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Applicant Id"},"type":{"type":"string","const":"link","title":"Type"},"url":{"type":"string","title":"Url"}},"additionalProperties":false,"type":"object","required":["key","type","url"],"title":"LinkDeliverable","description":"A URL the applicant follows (the authority's own status page)."},"ListGroupFieldDescriptor":{"properties":{"key":{"type":"string","title":"Key"},"label":{"additionalProperties":{"type":"string"},"type":"object","title":"Label"},"help":{"anyOf":[{"additionalProperties":{"type":"string"},"type":"object"},{"type":"null"}],"title":"Help"},"required":{"type":"boolean","title":"Required","default":false},"source":{"anyOf":[{"type":"string","enum":["passport","ocr","user","derived","fixed"]},{"type":"null"}],"title":"Source"},"sensitive":{"type":"boolean","title":"Sensitive","default":false},"validators":{"items":{"$ref":"#/components/schemas/ValidatorDescriptor"},"type":"array","title":"Validators"},"visible_if":{"anyOf":[{"$ref":"#/components/schemas/JsonValue"},{"type":"null"}]},"required_if":{"anyOf":[{"$ref":"#/components/schemas/JsonValue"},{"type":"null"}]},"type":{"type":"string","const":"list_group","title":"Type"},"min_items":{"type":"integer","title":"Min Items","default":0},"max_items":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Max Items"},"item_fields":{"items":{"oneOf":[{"$ref":"#/components/schemas/ScalarFieldDescriptor"},{"$ref":"#/components/schemas/SelectFieldDescriptor"},{"$ref":"#/components/schemas/ListGroupFieldDescriptor"}],"discriminator":{"propertyName":"type","mapping":{"bool":"#/components/schemas/ScalarFieldDescriptor","country":"#/components/schemas/ScalarFieldDescriptor","date":"#/components/schemas/ScalarFieldDescriptor","email":"#/components/schemas/ScalarFieldDescriptor","file":"#/components/schemas/ScalarFieldDescriptor","gender":"#/components/schemas/ScalarFieldDescriptor","int":"#/components/schemas/ScalarFieldDescriptor","list_group":"#/components/schemas/ListGroupFieldDescriptor","multiselect":"#/components/schemas/SelectFieldDescriptor","number":"#/components/schemas/ScalarFieldDescriptor","passport":"#/components/schemas/ScalarFieldDescriptor","phone":"#/components/schemas/ScalarFieldDescriptor","select":"#/components/schemas/SelectFieldDescriptor","text":"#/components/schemas/ScalarFieldDescriptor","textarea":"#/components/schemas/ScalarFieldDescriptor"}}},"type":"array","title":"Item Fields"}},"additionalProperties":false,"type":"object","required":["key","type"],"title":"ListGroupFieldDescriptor","description":"The ``list<group>`` primitive: a repeatable composite block (arch §3)."},"LoginLinkRedeemBody":{"properties":{"token":{"type":"string","title":"Token"}},"additionalProperties":false,"type":"object","required":["token"],"title":"LoginLinkRedeemBody","description":"\"Here is my link token\" — the whole credential, and nothing else (MLK4).\n\nDeliberately UNCONSTRAINED beyond its type. Every shape-level bound one could\nadd here (a ``min_length``, a ``lgl_`` pattern, a ceiling) would turn part of\nthe refusal surface into a 422 that a prober can tell apart from the flat 401 —\nand the flatness IS the requirement: an empty string, a foreign-space token and\na dead one must be answered identically. The verdict belongs to\n:func:`~trippy_core.auth.login_link.consume_login_link`, which turns away the\nempty string and the wrong prefix before touching the database anyway, so the\nlaxity here costs no query.\n\n``extra=\"forbid\"`` still applies, and a body with no ``token`` at all is the\none 422 that remains — a request that is not of this shape, carrying no\nstatement about any account."},"LoginLinkRedeemResponse":{"properties":{"authenticated":{"type":"boolean","title":"Authenticated"},"user":{"$ref":"#/components/schemas/SessionUser"},"return_to":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Return To"}},"additionalProperties":false,"type":"object","required":["authenticated","user"],"title":"LoginLinkRedeemResponse","description":"What a granted redeem answers with (MLK4).\n\n``user`` is the same projection :class:`SessionResponse` carries for the very\nsession this response plants, so the page that redeems does not have to make a\nsecond call to learn who it just signed in. ``return_to`` is the destination\nrecorded when the link was requested, re-validated on the way out — ``None``\nwhenever it did not survive that gate (or was never given), never a default\npath invented here: choosing where to land is the caller's business, and a\nserver-side fallback would quietly send a user somewhere he never asked for."},"LoginLinkRequestBody":{"properties":{"email":{"type":"string","maxLength":254,"minLength":5,"pattern":"^[^@\\s]+@[^@\\s]+\\.[^@\\s]+$","title":"Email"},"return_to":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Return To"}},"additionalProperties":false,"type":"object","required":["email"],"title":"LoginLinkRequestBody","description":"\"Send me a login link\" — an address and, optionally, where to land (MLK2).\n\nBoth fields are attacker-controlled (the surface is anonymous and unpriced), so\ntheir bounds are part of the contract rather than a nicety — the same posture as\nthe express start and the public support form.\n\nThe address is shape-checked with the same pattern the express start uses\n(``express/schemas.py``) rather than by an e-mail-validation dependency the\nrepo does not carry: the point is to reject something that cannot be an\naddress at all, not to decide deliverability — that is the letter's job. The\npattern is *not* enough on its own: ``[^@\\s]`` contains ``U+0000`` and the rest\nof the control class, so the shape carries the same fence every public write\nshape carries (:func:`~trippy_core.contracts.common.has_control_chars`). A NUL\nthat got past here would not store a funny row — Postgres refuses the character\nin a ``citext`` column, so the INSERT raises and a route that contractually\nanswers 202/429 hands an anonymous caller a 500 whose ``DBAPIError`` carries the\nstatement *and its parameters* into the log; the printable-looking rest of the\nclass (``\\x1b``, ``\\x7f``, C1) stores fine and quietly becomes the addressee of\nthe letter.\n\n``return_to`` is not fenced at the shape at all. Its validation — content AND\nlength — is the router's :func:`~trippy_core.auth.router._safe_return_to`\n(anti-open-redirect), and an unusable value must be *dropped* to NULL, never\nanswered with a 422: a request that fails differently depending on what was\ntyped is one more thing an enumerator can measure, and the value of a mere\ndestination hint must never cost a user his letter. Oversize is no exception:\n``_safe_return_to`` bounds what is *stored*, so a shape-level ``max_length``\nwould only turn a droppable value into a hard refusal."},"LoginLinkRequestResponse":{"properties":{"status":{"type":"string","title":"Status"}},"additionalProperties":false,"type":"object","required":["status"],"title":"LoginLinkRequestResponse","description":"The ONE answer the request route gives — same bytes for every address.\n\nA single field with a single value: an existing account, an unknown address\nand an erased one are all answered with this, byte for byte, because any\ndifference at all would turn the route into an account-existence oracle."},"LoginStartResponse":{"properties":{"authorization_url":{"type":"string","title":"Authorization Url"}},"additionalProperties":false,"type":"object","required":["authorization_url"],"title":"LoginStartResponse","description":"Where the browser is sent to begin the Google OAuth dance."},"MarketDirectionsResponse":{"properties":{"market":{"type":"string","title":"Market"},"express_checkout":{"type":"boolean","title":"Express Checkout","default":false},"directions":{"items":{"$ref":"#/components/schemas/DirectionSummary"},"type":"array","title":"Directions"}},"additionalProperties":false,"type":"object","required":["market"],"title":"MarketDirectionsResponse","description":"Directions available on one market — feeds the picker (arch §8)."},"MarketSummary":{"properties":{"code":{"type":"string","title":"Code"},"name":{"type":"string","title":"Name"},"locales":{"items":{"type":"string"},"type":"array","title":"Locales"},"currency":{"type":"string","title":"Currency"},"pricing_base_currency":{"type":"string","title":"Pricing Base Currency","default":"USD"},"presentment_currencies":{"items":{"type":"string"},"type":"array","title":"Presentment Currencies"},"express_checkout":{"type":"boolean","title":"Express Checkout","default":false},"directions":{"items":{"$ref":"#/components/schemas/DirectionSummary"},"type":"array","title":"Directions"}},"additionalProperties":false,"type":"object","required":["code","name","locales","currency"],"title":"MarketSummary"},"Money":{"properties":{"amount_minor":{"type":"integer","title":"Amount Minor"},"currency":{"type":"string","title":"Currency"}},"additionalProperties":false,"type":"object","required":["amount_minor","currency"],"title":"Money","description":"A monetary amount in **integer minor units** (arch §9 conventions)."},"NoAction":{"properties":{"type":{"type":"string","const":"none","title":"Type"}},"additionalProperties":false,"type":"object","required":["type"],"title":"NoAction"},"NoteDeliverable":{"properties":{"key":{"type":"string","title":"Key"},"label":{"type":"string","title":"Label","default":""},"help":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Help"},"per_applicant":{"type":"boolean","title":"Per Applicant","default":false},"applicant_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Applicant Id"},"type":{"type":"string","const":"note","title":"Type"},"text":{"type":"string","title":"Text","default":""}},"additionalProperties":false,"type":"object","required":["key","type"],"title":"NoteDeliverable","description":"Fixed localized copy shipped with the выдача (border rules, reminders)."},"OcrLocation":{"properties":{"page":{"type":"integer","title":"Page"},"rect":{"prefixItems":[{"type":"number"},{"type":"number"},{"type":"number"},{"type":"number"}],"type":"array","maxItems":4,"minItems":4,"title":"Rect"}},"additionalProperties":false,"type":"object","required":["page","rect"],"title":"OcrLocation","description":"A per-field bbox hint: WHERE on the scan a recognised value was read (U24e).\n\nThe wire projection of a D20 :data:`~config_engine.ocr_registry.LocationHint`\n(persisted under :data:`documents.ocr_persist.OCR_LOCATIONS_KEY`): the ``page``\nindex the value sits on and the normalised [0,1] ``rect`` (``(x0, y0, x1, y1)``,\nwell-ordered inside the unit square) the applicant UI highlights. Geometry, never\nPII — and only ever attached to a non-sensitive field that survived into\n:attr:`OcrRecognized.fields`."},"OcrPrefill":{"properties":{"answers":{"additionalProperties":{"type":"string"},"type":"object","title":"Answers"},"passport_last4":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Passport Last4"}},"additionalProperties":false,"type":"object","title":"OcrPrefill","description":"Owner-only prefill hint extracted from a passport scan (ADR-17 §1-3, D10).\n\nThe **extraction** counterpart of the verdict-only :class:`OcrResult`: the MRZ\nfields the applicant may accept into their own ``answers`` (last/first name,\nbirth date, gender, citizenship, passport expiry) as plaintext, PLUS the\npassport number **only as a last4 mask** — never its plaintext. This is the\nprivacy invariant of ADR-17 §3 baked into the type: the document number routes\nto the B5a sensitive store alone (as on manual entry), and what leaves the seam\nfor the owner's screen carries a mask, not the number. The raw MRZ stays\ntransient in worker memory (§10) — this derived, masked view is all that\nsurvives extraction. ``answers`` holds ONLY non-sensitive plaintext fields;\n``passport_number`` is structurally absent from it by construction."},"OcrRecognized":{"properties":{"fields":{"additionalProperties":{"type":"string"},"type":"object","title":"Fields"},"masked":{"additionalProperties":{"type":"string"},"type":"object","title":"Masked"},"labels":{"additionalProperties":{"type":"string"},"type":"object","title":"Labels"},"locations":{"additionalProperties":{"$ref":"#/components/schemas/OcrLocation"},"type":"object","title":"Locations"}},"additionalProperties":false,"type":"object","title":"OcrRecognized","description":"Additive owner+operator projection of a D21 structured OCR extract (U24).\n\nThe **generalisation** of the passport-only :class:`OcrPrefill` onto ANY OcrDoc\nkind: the recognised document fields D21 persisted into the ``ocr_result`` JSONB\n(``fields`` / ``masked``), surfaced for the owner's prefill panel AND the\noperator's card. This is a read-only PROJECTION of what is already durably\nstored — never a new egress:\n\n* :attr:`fields` — the declared **non-sensitive** keys as plaintext display\n  strings (a bool as ``true``/``false``, a date/number/text as-is). Both the\n  owner and an in-scope operator may see these.\n* :attr:`masked` — the declared **sensitive** keys as their ``last4`` mask\n  ONLY. The plaintext is never persisted (the B5e storage rule — it routes to\n  the KEK store at extraction time), so it is structurally absent here too:\n  neither the owner's screen nor the operator's ever receives the full value.\n* :attr:`locations` — the optional per-field bbox hint (slice U24e), for a\n  non-sensitive key that survived into :attr:`fields`, so either surface can\n  highlight WHERE on the scan the value was read. Geometry, never PII.\n\n``None`` from :meth:`from_stored` means the row carries no structured extract\n(a verdict-only row, or a legacy pre-D21 row) — the panel simply renders\nnothing, exactly as an absent OCR result does."},"OcrResult":{"properties":{"verdict":{"type":"string","enum":["match","mismatch","unreadable","skipped"],"title":"Verdict"},"last4":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Last4"},"doc_type":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Doc Type"},"expiry_ok":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Expiry Ok"}},"additionalProperties":false,"type":"object","required":["verdict"],"title":"OcrResult","description":"OCR verdict only — the raw MRZ is never persisted (arch §10 contract)."},"OperatorApplicantPatch":{"properties":{"external_application_ref":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"External Application Ref"},"visa_grant_number":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Visa Grant Number"}},"additionalProperties":false,"type":"object","title":"OperatorApplicantPatch","description":"Operator edit of the applicant's external submission artefacts (slice A9).\n\nThe CRM PATCHes the free-string references the operator reads off the\ngovernment portal: ``external_application_ref`` next to the FILING checklist,\n``visa_grant_number`` from the F14/F12b visa-result widget. Both optional and\nnullable — only fields actually present in the request body are written (an\nabsent field is left untouched, an explicit ``null`` clears it), so the two\ninputs can be saved independently at their own points in the case lifecycle.\nAt least one field must be supplied (enforced in the router)."},"OperatorApplicantView":{"properties":{"id":{"type":"string","title":"Id"},"idx":{"type":"integer","title":"Idx"},"full_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Full Name"},"citizenship":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Citizenship"},"passport_last4":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Passport Last4"},"outcome":{"$ref":"#/components/schemas/ApplicantOutcome"},"reject_reason":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Reject Reason"},"visa_result_document_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Visa Result Document Id"},"external_application_ref":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"External Application Ref"},"visa_grant_number":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Visa Grant Number"},"answers":{"$ref":"#/components/schemas/JsonValue"}},"additionalProperties":false,"type":"object","required":["id","idx","outcome","answers"],"title":"OperatorApplicantView"},"OperatorApplicationDetail":{"properties":{"id":{"type":"string","title":"Id"},"market":{"type":"string","title":"Market"},"direction":{"type":"string","title":"Direction"},"status":{"$ref":"#/components/schemas/InternalStatus"},"client_status":{"$ref":"#/components/schemas/ClientStatus"},"purchase_mode":{"anyOf":[{"$ref":"#/components/schemas/PurchaseMode"},{"type":"null"}]},"handoff_mode":{"anyOf":[{"$ref":"#/components/schemas/HandoffMode"},{"type":"null"}]},"tariff_key":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Tariff Key"},"assigned_to":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Assigned To"},"sla_due_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Sla Due At"},"pin":{"$ref":"#/components/schemas/VersionPinView"},"answers":{"$ref":"#/components/schemas/JsonValue"},"applicants":{"items":{"$ref":"#/components/schemas/OperatorApplicantView"},"type":"array","title":"Applicants"},"documents":{"items":{"$ref":"#/components/schemas/DocumentView"},"type":"array","title":"Documents"},"business_reviews":{"items":{"$ref":"#/components/schemas/BusinessReviewView"},"type":"array","title":"Business Reviews"},"payments":{"items":{"$ref":"#/components/schemas/PaymentView"},"type":"array","title":"Payments"},"checklists":{"items":{"$ref":"#/components/schemas/ChecklistItemState"},"type":"array","title":"Checklists"},"delivery":{"$ref":"#/components/schemas/OperatorDeliveryView"},"dossier_package":{"anyOf":[{"$ref":"#/components/schemas/DossierPackageView"},{"type":"null"}]},"tasks":{"items":{"$ref":"#/components/schemas/OperatorTaskView"},"type":"array","title":"Tasks"},"info_requests":{"items":{"$ref":"#/components/schemas/InfoRequestView"},"type":"array","title":"Info Requests"},"auto_processing":{"anyOf":[{"type":"string","enum":["queued","degraded","completed"]},{"type":"null"}],"title":"Auto Processing"},"auto_processing_reason":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Auto Processing Reason"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"updated_at":{"type":"string","format":"date-time","title":"Updated At"}},"additionalProperties":false,"type":"object","required":["id","market","direction","status","client_status","pin","answers","created_at","updated_at"],"title":"OperatorApplicationDetail"},"OperatorApplicationList":{"properties":{"items":{"items":{"$ref":"#/components/schemas/OperatorApplicationSummary"},"type":"array","title":"Items"}},"additionalProperties":false,"type":"object","title":"OperatorApplicationList"},"OperatorApplicationSummary":{"properties":{"id":{"type":"string","title":"Id"},"market":{"type":"string","title":"Market"},"direction":{"type":"string","title":"Direction"},"status":{"$ref":"#/components/schemas/InternalStatus"},"assigned_to":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Assigned To"},"sla_due_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Sla Due At"},"applicant_count":{"type":"integer","title":"Applicant Count"},"ready_applicant_count":{"type":"integer","title":"Ready Applicant Count"},"updated_at":{"type":"string","format":"date-time","title":"Updated At"},"auto_processing":{"anyOf":[{"type":"string","enum":["queued","degraded","completed"]},{"type":"null"}],"title":"Auto Processing"}},"additionalProperties":false,"type":"object","required":["id","market","direction","status","applicant_count","ready_applicant_count","updated_at"],"title":"OperatorApplicationSummary"},"OperatorDeliveryView":{"properties":{"items":{"items":{"$ref":"#/components/schemas/DeliverableStateView"},"type":"array","title":"Items"}},"additionalProperties":false,"type":"object","title":"OperatorDeliveryView","description":"The case's выдача — every item of the pinned spec, in authored order."},"OperatorEvent":{"type":"string","enum":["start_review","request_info","mark_ready_to_file","start_filing","mark_filed","deliver","refund","close","cancel","repin","slot_booked","book_itinerary","slot_failed"],"title":"OperatorEvent","description":"FSM events an operator can drive through ``transition()`` (arch §4)."},"OperatorPresignRequest":{"properties":{"requirement_key":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Requirement Key"},"applicant_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Applicant Id"},"filename":{"type":"string","title":"Filename"},"content_type":{"type":"string","title":"Content Type"},"size_bytes":{"type":"integer","title":"Size Bytes"}},"additionalProperties":false,"type":"object","required":["filename","content_type","size_bytes"],"title":"OperatorPresignRequest","description":"The operator's upload grant request for a deliverable (slice DLV3).\n\nSplit from the applicant :class:`PresignRequest` because the two paths differ on\nexactly one field: an applicant MUST name the intake requirement they are\nsatisfying, while the operator's key is **optional**.\n\n* ``requirement_key`` omitted (or blank) ⇒ the case's ``carries_outcome``\n  delivery item — the legacy ``visa_result`` for any direction that declares no\n  ``delivery`` section. This is what keeps today's CRM (which knows only the one\n  hard-coded key) working untouched: the front is not part of this slice.\n* ``requirement_key`` given ⇒ it must be a ``file`` item of the case's PINNED\n  delivery spec, else 422. That is what lets a direction issue more than one\n  artifact (the visa PDF *and* the insurance policy) without a code change."},"OperatorSupportMessageCreate":{"properties":{"body":{"type":"string","maxLength":8000,"minLength":1,"title":"Body"},"visibility":{"type":"string","enum":["public","internal"],"title":"Visibility","default":"public"},"client_message_id":{"anyOf":[{"type":"string","maxLength":128,"minLength":1},{"type":"null"}],"title":"Client Message Id"}},"additionalProperties":false,"type":"object","required":["body"],"title":"OperatorSupportMessageCreate","description":"An operator's reply — or an internal note nobody outside staff ever sees.\n\n``visibility='internal'`` is the whole point of the field: notes about a\nclient (\"похоже на дубль, проверить платёж\") have no home in the CRM today, so\nthey end up in Telegram or nowhere. An internal note is *not* an answer: it\ndoes not move the thread to ``pending_user`` and notifies nobody (§13)."},"OperatorSupportMessageView":{"properties":{"id":{"type":"string","title":"Id"},"author_type":{"type":"string","title":"Author Type"},"author_staff_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Author Staff Id"},"visibility":{"type":"string","title":"Visibility"},"body":{"type":"string","title":"Body"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"author_is_bot":{"type":"boolean","title":"Author Is Bot","default":false}},"additionalProperties":false,"type":"object","required":["id","author_type","visibility","body","created_at"],"title":"OperatorSupportMessageView","description":"One message as an **operator** sees it — including internal notes.\n\nThe client's :class:`SupportMessageView` has no ``visibility`` field at all;\nthis one carries it because an operator must be able to tell his own private\nnote from what the client actually received. Two shapes, not one shape with a\nflag: the client projection cannot be made to render an internal note even by\nmistake."},"OperatorSupportThreadDetail":{"properties":{"thread":{"$ref":"#/components/schemas/OperatorSupportThreadView"},"messages":{"items":{"$ref":"#/components/schemas/OperatorSupportMessageView"},"type":"array","title":"Messages"},"next_cursor":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Next Cursor"}},"additionalProperties":false,"type":"object","required":["thread","messages"],"title":"OperatorSupportThreadDetail","description":"The operator thread card: the thread plus its FULL transcript."},"OperatorSupportThreadList":{"properties":{"items":{"items":{"$ref":"#/components/schemas/OperatorSupportThreadView"},"type":"array","title":"Items"},"next_cursor":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Next Cursor"},"counts":{"$ref":"#/components/schemas/SupportInboxCounts"}},"additionalProperties":false,"type":"object","required":["items","counts"],"title":"OperatorSupportThreadList","description":"One keyset page of the operator inbox plus its badge counters."},"OperatorSupportThreadView":{"properties":{"id":{"type":"string","title":"Id"},"status":{"type":"string","title":"Status"},"subject":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Subject"},"market_code":{"type":"string","title":"Market Code"},"source":{"type":"string","title":"Source"},"application_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Application Id"},"user_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"User Id"},"assignee_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Assignee Id"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"last_message_at":{"type":"string","format":"date-time","title":"Last Message At"},"last_user_message_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Last User Message At"},"staff_read_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Staff Read At"},"staff_read_by":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Staff Read By"},"unread":{"type":"boolean","title":"Unread","default":false},"first_response_due_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"First Response Due At"},"first_responded_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"First Responded At"},"first_response_sla":{"type":"string","title":"First Response Sla","default":"none"},"bot_escalated_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Bot Escalated At"}},"additionalProperties":false,"type":"object","required":["id","status","market_code","source","created_at","last_message_at"],"title":"OperatorSupportThreadView","description":"A thread as the operator inbox shows it (slice SUP9).\n\nCarries the working columns the client projection deliberately omits —\n``assignee_id``, ``staff_read_at``/``staff_read_by``, ``source``,\n``last_user_message_at`` — but still no ``guest_token_hash``: that hash is an\nauthentication secret's shadow, and no screen has a reason for it.\n``unread`` is served, not derived on the client, so the badge and the\n``unread=true`` filter can never disagree."},"OperatorTaskView":{"properties":{"id":{"type":"string","title":"Id"},"type":{"type":"string","enum":["review","file","recheck"],"title":"Type"},"status":{"type":"string","enum":["open","done"],"title":"Status"},"assignee_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Assignee Id"},"due_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Due At"}},"additionalProperties":false,"type":"object","required":["id","type","status"],"title":"OperatorTaskView"},"OptionDescriptor":{"properties":{"value":{"type":"string","title":"Value"},"label":{"additionalProperties":{"type":"string"},"type":"object","title":"Label"}},"additionalProperties":false,"type":"object","required":["value"],"title":"OptionDescriptor"},"PaymentKind":{"type":"string","enum":["subscription","gov_fee","one_off","upsell","refund"],"title":"PaymentKind"},"PaymentListResponse":{"properties":{"items":{"items":{"$ref":"#/components/schemas/PaymentView"},"type":"array","title":"Items"},"order_state":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Order State"}},"additionalProperties":false,"type":"object","title":"PaymentListResponse"},"PaymentStage":{"properties":{"key":{"type":"string","title":"Key"},"title":{"additionalProperties":{"type":"string"},"type":"object","title":"Title"},"visible_if":{"anyOf":[{"$ref":"#/components/schemas/JsonValue"},{"type":"null"}]},"type":{"type":"string","const":"payment","title":"Type"},"purchase_modes":{"items":{"$ref":"#/components/schemas/PurchaseMode"},"type":"array","title":"Purchase Modes"}},"additionalProperties":false,"type":"object","required":["key","type"],"title":"PaymentStage"},"PaymentStatus":{"type":"string","enum":["pending","requires_action","succeeded","failed","disputed","refunded"],"title":"PaymentStatus"},"PaymentView":{"properties":{"id":{"type":"string","title":"Id"},"kind":{"$ref":"#/components/schemas/PaymentKind"},"status":{"$ref":"#/components/schemas/PaymentStatus"},"amount":{"$ref":"#/components/schemas/Money"},"presentment_amount":{"anyOf":[{"$ref":"#/components/schemas/Money"},{"type":"null"}]},"application_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Application Id"},"applicant_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Applicant Id"},"parent_payment_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Parent Payment Id"},"created_at":{"type":"string","format":"date-time","title":"Created At"}},"additionalProperties":false,"type":"object","required":["id","kind","status","amount","created_at"],"title":"PaymentView"},"PerApplicantOutcome":{"properties":{"applicant_id":{"type":"string","title":"Applicant Id"},"outcome":{"$ref":"#/components/schemas/ApplicantOutcome"},"reject_reason":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Reject Reason"},"visa_result_document_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Visa Result Document Id"}},"additionalProperties":false,"type":"object","required":["applicant_id","outcome"],"title":"PerApplicantOutcome"},"PrepaidDraftList":{"properties":{"items":{"items":{"$ref":"#/components/schemas/PrepaidDraftView"},"type":"array","title":"Items"},"total":{"type":"integer","title":"Total"},"limit":{"type":"integer","title":"Limit"},"offset":{"type":"integer","title":"Offset"}},"additionalProperties":false,"type":"object","required":["items","total","limit","offset"],"title":"PrepaidDraftList","description":"A page of prepaid drafts plus the total over the whole filtered set."},"PrepaidDraftView":{"properties":{"application_id":{"type":"string","title":"Application Id"},"owner_email":{"type":"string","title":"Owner Email"},"market_code":{"type":"string","title":"Market Code"},"direction_slug":{"type":"string","title":"Direction Slug"},"amount_minor":{"type":"integer","title":"Amount Minor"},"currency":{"type":"string","title":"Currency"},"paid_at":{"type":"string","format":"date-time","title":"Paid At"},"idle_seconds":{"type":"integer","title":"Idle Seconds"}},"additionalProperties":false,"type":"object","required":["application_id","owner_email","market_code","direction_slug","amount_minor","currency","paid_at","idle_seconds"],"title":"PrepaidDraftView","description":"One prepaid draft. The field set is closed — nothing beyond these eight."},"PresignRequest":{"properties":{"requirement_key":{"type":"string","title":"Requirement Key"},"applicant_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Applicant Id"},"filename":{"type":"string","title":"Filename"},"content_type":{"type":"string","title":"Content Type"},"size_bytes":{"type":"integer","title":"Size Bytes"}},"additionalProperties":false,"type":"object","required":["requirement_key","filename","content_type","size_bytes"],"title":"PresignRequest"},"PresignResponse":{"properties":{"document_id":{"type":"string","title":"Document Id"},"upload_url":{"type":"string","title":"Upload Url"},"method":{"type":"string","enum":["PUT","POST"],"title":"Method","default":"PUT"},"fields":{"additionalProperties":{"type":"string"},"type":"object","title":"Fields"},"s3_key":{"type":"string","title":"S3 Key"},"max_bytes":{"type":"integer","title":"Max Bytes"},"expires_at":{"type":"string","format":"date-time","title":"Expires At"}},"additionalProperties":false,"type":"object","required":["document_id","upload_url","s3_key","max_bytes","expires_at"],"title":"PresignResponse"},"PreviewResultView":{"properties":{"ok":{"type":"boolean","title":"Ok"},"errors":{"items":{"$ref":"#/components/schemas/ConfigProblemView"},"type":"array","title":"Errors"},"diffs":{"items":{"$ref":"#/components/schemas/FlowDiffView"},"type":"array","title":"Diffs"}},"additionalProperties":false,"type":"object","required":["ok"],"title":"PreviewResultView"},"PricingDescriptor":{"properties":{"purchase_modes":{"items":{"$ref":"#/components/schemas/PurchaseMode"},"type":"array","title":"Purchase Modes"},"service_fee":{"anyOf":[{"$ref":"#/components/schemas/ServiceFeeDescriptor"},{"type":"null"}]},"tariffs":{"items":{"$ref":"#/components/schemas/TariffDescriptor"},"type":"array","title":"Tariffs"},"gov_fee":{"anyOf":[{"$ref":"#/components/schemas/GovFeeDescriptor"},{"type":"null"}]},"subscription_plans":{"items":{"$ref":"#/components/schemas/SubscriptionPlanDescriptor"},"type":"array","title":"Subscription Plans"},"base_discount_percent":{"type":"integer","maximum":99.0,"minimum":0.0,"title":"Base Discount Percent","default":0},"referral_discount_percent":{"anyOf":[{"type":"integer","maximum":30.0,"minimum":1.0},{"type":"null"}],"title":"Referral Discount Percent"}},"additionalProperties":false,"type":"object","title":"PricingDescriptor"},"PromoCodeResponse":{"properties":{"code":{"type":"string","title":"Code"},"status":{"type":"string","enum":["available","redeemed","expired","revoked"],"title":"Status"},"issued_at":{"type":"string","format":"date-time","title":"Issued At"},"expires_at":{"type":"string","format":"date-time","title":"Expires At"},"source":{"type":"string","const":"referral_reward","title":"Source"}},"additionalProperties":false,"type":"object","required":["code","status","issued_at","expires_at","source"],"title":"PromoCodeResponse","description":"One personal promo code without referral PII."},"PromoInventoryResponse":{"properties":{"codes":{"items":{"$ref":"#/components/schemas/PromoCodeResponse"},"type":"array","title":"Codes"}},"additionalProperties":false,"type":"object","required":["codes"],"title":"PromoInventoryResponse","description":"All promo codes owned by the authenticated applicant."},"PublicFxSnapshot":{"properties":{"source":{"type":"string","const":"ECB","title":"Source","default":"ECB"},"base":{"type":"string","const":"USD","title":"Base","default":"USD"},"publication_date":{"type":"string","format":"date","title":"Publication Date"},"rates":{"additionalProperties":{"type":"string"},"propertyNames":{"enum":["USD","EUR","TRY","INR"]},"type":"object","title":"Rates"}},"additionalProperties":false,"type":"object","required":["publication_date","rates"],"title":"PublicFxSnapshot"},"PublicSupportConfig":{"properties":{"market_code":{"type":"string","title":"Market Code"},"locale":{"type":"string","title":"Locale"},"kind":{"type":"string","enum":["aydinlatma_ack","dpdp_notice_ack"],"title":"Kind"},"text_version":{"type":"string","title":"Text Version"},"text":{"type":"string","title":"Text"}},"additionalProperties":false,"type":"object","required":["market_code","locale","kind","text_version","text"],"title":"PublicSupportConfig","description":"The exact consent notice the guest form may acknowledge.\n\nThis is deliberately a public projection, not a market profile: immutable pin\nids, profile hashes and configuration are server-owned and never cross this\nboundary."},"PublicSupportMessageAccepted":{"properties":{"message_id":{"type":"string","title":"Message Id"},"duplicate":{"type":"boolean","title":"Duplicate","default":false}},"additionalProperties":false,"type":"object","required":["message_id"],"title":"PublicSupportMessageAccepted","description":"Answer to a public follow-up: the id of the stored message.\n\n``duplicate=True`` means the ``client_message_id`` was already used, so the\nexisting row is read back instead of a second message being appended."},"PublicSupportMessageCreate":{"properties":{"body":{"type":"string","maxLength":8000,"minLength":1,"title":"Body"},"client_message_id":{"anyOf":[{"type":"string","maxLength":128,"minLength":1},{"type":"null"}],"title":"Client Message Id"},"website":{"anyOf":[{"type":"string","maxLength":4000},{"type":"null"}],"title":"Website"}},"additionalProperties":false,"type":"object","required":["body"],"title":"PublicSupportMessageCreate","description":"A guest's follow-up message, authenticated by the thread token (SUP7)."},"PublicSupportThreadAccepted":{"properties":{"thread_id":{"type":"string","title":"Thread Id"}},"additionalProperties":false,"type":"object","required":["thread_id"],"title":"PublicSupportThreadAccepted","description":"Answer to a public form submit: the id of the thread the request opened.\n\nOne field, and deliberately only one — a status or a timestamp here would be\nstate the form does not need and the read route already serves under the token.\n\n``202``, not ``201``: what the guest actually needs — the e-mailed link that\nsurvives a lost cookie (SUP10) — is queued, not delivered, by the time this\nreturns."},"PublicSupportThreadCreate":{"properties":{"market_code":{"type":"string","maxLength":16,"minLength":2,"title":"Market Code"},"contact_email":{"type":"string","maxLength":254,"minLength":5,"pattern":"^[^@\\s]+@[^@\\s]+\\.[^@\\s]+$","title":"Contact Email"},"body":{"type":"string","maxLength":8000,"minLength":1,"title":"Body"},"subject":{"anyOf":[{"type":"string","maxLength":200,"minLength":1},{"type":"null"}],"title":"Subject"},"contact_locale":{"anyOf":[{"type":"string","maxLength":8,"minLength":2},{"type":"null"}],"title":"Contact Locale"},"consent_ack":{"type":"boolean","title":"Consent Ack","default":false},"website":{"anyOf":[{"type":"string","maxLength":4000},{"type":"null"}],"title":"Website"}},"additionalProperties":false,"type":"object","required":["market_code","contact_email","body"],"title":"PublicSupportThreadCreate","description":"The public support form (slice SUP7, SPEC-SUPPORT §9/§10).\n\nThe **unauthenticated** write shape, so every field here is attacker-controlled\nand the bounds are the contract, not a nicety: ``body`` and ``subject`` inherit\nthe same caps as the account path, and ``contact_email`` is bounded and\nshape-checked because it is the only way an answer ever reaches this person.\n\n``consent_ack`` is the acknowledgement **fact** and nothing else — no text, no\nhash, no version (SPEC §b rule 1): the server resolves the market's published\nprofile pin itself and recomputes the hash from the wording that pin carries.\nAn un-ticked box is refused (fail-closed: no consent → no thread, §9)."},"PublishRequest":{"properties":{"confirm_desensitize":{"items":{"type":"string"},"type":"array","title":"Confirm Desensitize"}},"additionalProperties":false,"type":"object","title":"PublishRequest","description":"Optional publish body carrying the §10 desensitization confirmation (CFG14).\n\n``confirm_desensitize`` echoes the exact field paths the editor accepts\nclearing ``sensitive`` off — a field sensitive in the last published version\nthat loses its flag. An empty/absent body means \"confirm nothing\": the publish\nis refused (409, ``requires_confirmation``) the moment it would retroactively\ndesensitize any PII field, so a silent downgrade is impossible."},"PublishResultView":{"properties":{"layer":{"type":"string","title":"Layer"},"target":{"type":"string","title":"Target"},"version":{"type":"integer","title":"Version"},"version_id":{"type":"string","title":"Version Id"}},"additionalProperties":false,"type":"object","required":["layer","target","version","version_id"],"title":"PublishResultView","description":"One immutable version a publish or rollback appended."},"PurchaseMode":{"type":"string","enum":["one_off","subscription"],"title":"PurchaseMode","description":"Purchase model, a property of the application (arch §9)."},"RedirectAction":{"properties":{"type":{"type":"string","const":"redirect","title":"Type"},"redirect_url":{"type":"string","title":"Redirect Url"}},"additionalProperties":false,"type":"object","required":["type","redirect_url"],"title":"RedirectAction"},"ReferralClaimRequest":{"properties":{"code":{"type":"string","maxLength":64,"minLength":4,"title":"Code"}},"additionalProperties":false,"type":"object","required":["code"],"title":"ReferralClaimRequest","description":"A referral code captured from an invite link."},"ReferralClaimResponse":{"properties":{"status":{"type":"string","enum":["claimed","already_claimed"],"title":"Status"},"referrer_code":{"type":"string","title":"Referrer Code"}},"additionalProperties":false,"type":"object","required":["status","referrer_code"],"title":"ReferralClaimResponse","description":"Result of binding the authenticated user to an inviter."},"ReferralSummaryResponse":{"properties":{"code":{"type":"string","title":"Code"},"invited":{"type":"integer","title":"Invited"},"pending":{"type":"integer","title":"Pending"},"qualified":{"type":"integer","title":"Qualified"},"rewarded":{"type":"integer","title":"Rewarded"}},"additionalProperties":false,"type":"object","required":["code","invited","pending","qualified","rewarded"],"title":"ReferralSummaryResponse","description":"The current user's invite code and invite lifecycle counters."},"RetryResult":{"properties":{"id":{"type":"integer","title":"Id"},"event_type":{"type":"string","title":"Event Type"},"retried":{"type":"boolean","title":"Retried","default":true}},"additionalProperties":false,"type":"object","required":["id","event_type"],"title":"RetryResult","description":"Outcome of a manual retry — the revived row is queued for the next sweep."},"ReusableDocumentList":{"properties":{"items":{"items":{"$ref":"#/components/schemas/ReusableDocumentView"},"type":"array","title":"Items"}},"additionalProperties":false,"type":"object","title":"ReusableDocumentList","description":"The verified documents a saved traveller can re-offer, newest per requirement."},"ReusableDocumentView":{"properties":{"id":{"type":"string","title":"Id"},"requirement_key":{"type":"string","title":"Requirement Key"},"status":{"$ref":"#/components/schemas/DocumentStatus"},"mime":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Mime"},"uploaded_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Uploaded At"},"source_application_id":{"type":"string","title":"Source Application Id"},"expires_on":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"Expires On"}},"additionalProperties":false,"type":"object","required":["id","requirement_key","status","source_application_id"],"title":"ReusableDocumentView","description":"One verified document a saved traveller offers for reuse into a new trip (D14).\n\nThe card owner has already uploaded and verified this scan on an earlier\napplication; the Documents step of a new trip can «use» it (a server-side copy\ninto the new slot) instead of asking for the passport / biometric again.\n``expires_on`` carries the passport's expiry when the slot is a passport — the UI\nlabels a scan «verified, expires 2030-01-01»; a lapsed passport never appears\nhere at all. ``None`` marks a document that cannot lapse (a biometric photo)."},"ReuseDocumentRequest":{"properties":{"application_id":{"type":"string","title":"Application Id"},"applicant_id":{"type":"string","title":"Applicant Id"},"requirement_key":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Requirement Key"}},"additionalProperties":false,"type":"object","required":["application_id","applicant_id"],"title":"ReuseDocumentRequest","description":"Copy one of a card's verified documents into a slot of the caller's draft.\n\n``application_id`` / ``applicant_id`` address the target roster slot the reused\ncopy is bound to; ``requirement_key`` overrides the slot label when the new\ndirection names it differently, else the source's requirement is carried over."},"ReuseDocumentResponse":{"properties":{"document_id":{"type":"string","title":"Document Id"},"requirement_key":{"type":"string","title":"Requirement Key"},"status":{"$ref":"#/components/schemas/DocumentStatus"}},"additionalProperties":false,"type":"object","required":["document_id","requirement_key","status"],"title":"ReuseDocumentResponse","description":"The fresh, independently-owned document the reuse copy materialised into."},"ReviewStage":{"properties":{"key":{"type":"string","title":"Key"},"title":{"additionalProperties":{"type":"string"},"type":"object","title":"Title"},"visible_if":{"anyOf":[{"$ref":"#/components/schemas/JsonValue"},{"type":"null"}]},"type":{"type":"string","const":"review","title":"Type"}},"additionalProperties":false,"type":"object","required":["key","type"],"title":"ReviewStage"},"RollbackRequest":{"properties":{"version":{"type":"integer","title":"Version"},"confirm_desensitize":{"items":{"type":"string"},"type":"array","title":"Confirm Desensitize"}},"additionalProperties":false,"type":"object","required":["version"],"title":"RollbackRequest"},"SaveStageRequest":{"properties":{"answers":{"anyOf":[{"$ref":"#/components/schemas/JsonValue"},{"type":"null"}]},"applicants":{"anyOf":[{"items":{"$ref":"#/components/schemas/ApplicantAnswers"},"type":"array"},{"type":"null"}],"title":"Applicants"},"tariff_key":{"anyOf":[{"type":"string","maxLength":64,"minLength":1},{"type":"null"}],"title":"Tariff Key"},"client_request_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Client Request Id"}},"additionalProperties":false,"type":"object","title":"SaveStageRequest","description":"Autosave of one stage. Allowed in DRAFT **and** NEEDS_INFO (arch §5)."},"ScalarDeliverable":{"properties":{"key":{"type":"string","title":"Key"},"label":{"type":"string","title":"Label","default":""},"help":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Help"},"per_applicant":{"type":"boolean","title":"Per Applicant","default":false},"applicant_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Applicant Id"},"type":{"type":"string","enum":["text","textarea","email","phone","int","number","bool","date","country"],"title":"Type"},"value":{"$ref":"#/components/schemas/JsonValue"}},"additionalProperties":false,"type":"object","required":["key","type"],"title":"ScalarDeliverable","description":"A typed scalar the operator recorded (a code, a date, a flag)."},"ScalarFieldDescriptor":{"properties":{"key":{"type":"string","title":"Key"},"label":{"additionalProperties":{"type":"string"},"type":"object","title":"Label"},"help":{"anyOf":[{"additionalProperties":{"type":"string"},"type":"object"},{"type":"null"}],"title":"Help"},"required":{"type":"boolean","title":"Required","default":false},"source":{"anyOf":[{"type":"string","enum":["passport","ocr","user","derived","fixed"]},{"type":"null"}],"title":"Source"},"sensitive":{"type":"boolean","title":"Sensitive","default":false},"validators":{"items":{"$ref":"#/components/schemas/ValidatorDescriptor"},"type":"array","title":"Validators"},"visible_if":{"anyOf":[{"$ref":"#/components/schemas/JsonValue"},{"type":"null"}]},"required_if":{"anyOf":[{"$ref":"#/components/schemas/JsonValue"},{"type":"null"}]},"type":{"type":"string","enum":["text","textarea","email","phone","passport","date","int","number","bool","country","gender","file"],"title":"Type"}},"additionalProperties":false,"type":"object","required":["key","type"],"title":"ScalarFieldDescriptor"},"SectionDescriptor":{"properties":{"key":{"type":"string","title":"Key"},"title":{"additionalProperties":{"type":"string"},"type":"object","title":"Title"},"help":{"anyOf":[{"additionalProperties":{"type":"string"},"type":"object"},{"type":"null"}],"title":"Help"},"fields":{"items":{"type":"string"},"type":"array","title":"Fields"}},"additionalProperties":false,"type":"object","required":["key"],"title":"SectionDescriptor","description":"A titled block of one stage's fields — presentation only (slice IFS1).\n\n``fields`` carries field *keys*, not descriptors: the stage's ``fields`` array\nstays the single place a field is described, so a client cannot end up with\ntwo divergent copies of the same control. A renderer walks the sections and\nlooks each key up in that array.\n\nAbsent (``sections: null``) on every stage that did not opt in, which is how a\nclient tells \"render as one flat run, as before\" from \"render these blocks\"."},"SelectDeliverable":{"properties":{"key":{"type":"string","title":"Key"},"label":{"type":"string","title":"Label","default":""},"help":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Help"},"per_applicant":{"type":"boolean","title":"Per Applicant","default":false},"applicant_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Applicant Id"},"type":{"type":"string","enum":["select","multiselect"],"title":"Type"},"labels":{"items":{"type":"string"},"type":"array","title":"Labels"}},"additionalProperties":false,"type":"object","required":["key","type"],"title":"SelectDeliverable","description":"A choice from the direction's option set — labels only, never the codes.\n\n``labels`` holds the localized label of each selected option in selection\norder (one entry for ``select``, zero or more for ``multiselect``), empty\nwhile unfilled. An option that vanished from the config since it was written\ndegrades to its raw value rather than disappearing — the applicant still sees\n*something* recorded."},"SelectFieldDescriptor":{"properties":{"key":{"type":"string","title":"Key"},"label":{"additionalProperties":{"type":"string"},"type":"object","title":"Label"},"help":{"anyOf":[{"additionalProperties":{"type":"string"},"type":"object"},{"type":"null"}],"title":"Help"},"required":{"type":"boolean","title":"Required","default":false},"source":{"anyOf":[{"type":"string","enum":["passport","ocr","user","derived","fixed"]},{"type":"null"}],"title":"Source"},"sensitive":{"type":"boolean","title":"Sensitive","default":false},"validators":{"items":{"$ref":"#/components/schemas/ValidatorDescriptor"},"type":"array","title":"Validators"},"visible_if":{"anyOf":[{"$ref":"#/components/schemas/JsonValue"},{"type":"null"}]},"required_if":{"anyOf":[{"$ref":"#/components/schemas/JsonValue"},{"type":"null"}]},"type":{"type":"string","enum":["select","multiselect"],"title":"Type"},"options":{"items":{"$ref":"#/components/schemas/OptionDescriptor"},"type":"array","title":"Options"},"options_source":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Options Source"}},"additionalProperties":false,"type":"object","required":["key","type"],"title":"SelectFieldDescriptor"},"SensitiveFieldMask":{"properties":{"field_key":{"type":"string","title":"Field Key"},"last4":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Last4"}},"additionalProperties":false,"type":"object","required":["field_key"],"title":"SensitiveFieldMask","description":"Non-secret projection of a stored sensitive field (slice B5e, §10).\n\nThe masked prefill the owning applicant sees for a ``sensitive`` field whose\nciphertext is already in the encrypted store: it carries only the search-safe\n``last4`` (NULL for a value shorter than four chars — the client then renders a\nbare «saved» chip), never the plaintext, which requires the privileged, audited\n:meth:`SensitiveStore.read`. Its presence lets the client show «•••• 1234»\ninstead of an empty passport box on a resumed draft."},"SensitiveWriteRequest":{"properties":{"value":{"type":"string","title":"Value"},"applicant_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Applicant Id"}},"additionalProperties":false,"type":"object","required":["value"],"title":"SensitiveWriteRequest","description":"Body of ``PUT /applications/{id}/sensitive/{field_key}`` (slice B5e, §10).\n\nThe single plaintext ``value`` (e.g. a passport number) the owning applicant\ntyped for one ``sensitive`` field, plus the optional ``applicant_id`` for a\nper-applicant field (an ``applicant_group`` roster entry). The server encrypts\nit through the B5a :class:`SensitiveStore` and returns only the mask — the\nplaintext never round-trips back."},"SeoPageResponse":{"properties":{"market":{"type":"string","title":"Market"},"direction":{"type":"string","title":"Direction"},"locale":{"type":"string","title":"Locale"},"title":{"type":"string","title":"Title"},"hero":{"$ref":"#/components/schemas/JsonValue"},"faq":{"$ref":"#/components/schemas/JsonValue"},"canonical":{"type":"string","title":"Canonical"},"hreflang":{"additionalProperties":{"type":"string"},"type":"object","title":"Hreflang"}},"additionalProperties":false,"type":"object","required":["market","direction","locale","title","hero","faq","canonical"],"title":"SeoPageResponse","description":"A direction SEO page — indexable, separate from the app flow (arch §8)."},"ServiceFeeDescriptor":{"properties":{"per":{"type":"string","enum":["applicant","application"],"title":"Per"},"amounts":{"items":{"$ref":"#/components/schemas/Money"},"type":"array","title":"Amounts"}},"additionalProperties":false,"type":"object","required":["per"],"title":"ServiceFeeDescriptor"},"ServicePaymentState":{"type":"string","enum":["unpaid","pending","paid","partially_refunded","refunding","refunded","disputed","failed"],"title":"ServicePaymentState","description":"Applicant-facing state of the latest order's service leg (GEC20)."},"ServicePaymentSummary":{"properties":{"state":{"$ref":"#/components/schemas/ServicePaymentState","default":"unpaid"},"paid_amount":{"anyOf":[{"$ref":"#/components/schemas/Money"},{"type":"null"}]},"presentment_amount":{"anyOf":[{"$ref":"#/components/schemas/Money"},{"type":"null"}]},"refunded_amount":{"anyOf":[{"$ref":"#/components/schemas/Money"},{"type":"null"}]},"paid_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Paid At"}},"additionalProperties":false,"type":"object","title":"ServicePaymentSummary","description":"Money-safe account projection; never a user-level entitlement flag."},"SessionResponse":{"properties":{"authenticated":{"type":"boolean","title":"Authenticated"},"user":{"anyOf":[{"$ref":"#/components/schemas/SessionUser"},{"type":"null"}]}},"additionalProperties":false,"type":"object","required":["authenticated"],"title":"SessionResponse"},"SessionUser":{"properties":{"id":{"type":"string","title":"Id"},"email":{"type":"string","title":"Email"},"locale":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Locale"},"markets":{"items":{"type":"string"},"type":"array","title":"Markets"}},"additionalProperties":false,"type":"object","required":["id","email"],"title":"SessionUser"},"SetupIntentResponse":{"properties":{"customer_id":{"type":"string","title":"Customer Id"},"client_secret":{"type":"string","title":"Client Secret"}},"additionalProperties":false,"type":"object","required":["customer_id","client_secret"],"title":"SetupIntentResponse"},"SlotStage":{"type":"string","enum":["expected","hunting","booked","itinerary_booked","failed"],"title":"SlotStage","description":"Where a slot-hunted case stands in the appointment chain (slice SH1).\n\nA **client** vocabulary, sibling of :class:`ClientStatus` and never of\n:class:`InternalStatus`: it rides ALONGSIDE the status instead of extending it,\nso the ЛК keeps reading ``processing`` through the whole chain (arch §4) while\nstill being told the slot is being hunted / booked / lost. ``None`` (absent\nstage) covers every case the chain does not concern — before submit, after the\nchain, and any case that files directly."},"StaffActivationChallenge":{"properties":{"email":{"type":"string","title":"Email"},"name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Name"},"role":{"$ref":"#/components/schemas/StaffRole"},"otpauth_uri":{"type":"string","title":"Otpauth Uri"}},"additionalProperties":false,"type":"object","required":["email","role","otpauth_uri"],"title":"StaffActivationChallenge","description":"What the activation page needs to render the QR and name the account."},"StaffActivationComplete":{"properties":{"token":{"type":"string","title":"Token"},"password":{"type":"string","maxLength":256,"minLength":12,"title":"Password"},"code":{"type":"string","maxLength":10,"minLength":6,"title":"Code"}},"additionalProperties":false,"type":"object","required":["token","password","code"],"title":"StaffActivationComplete"},"StaffActivationStart":{"properties":{"token":{"type":"string","title":"Token"}},"additionalProperties":false,"type":"object","required":["token"],"title":"StaffActivationStart"},"StaffInviteCreate":{"properties":{"email":{"type":"string","maxLength":320,"minLength":3,"title":"Email"},"name":{"anyOf":[{"type":"string","maxLength":200},{"type":"null"}],"title":"Name"},"role":{"$ref":"#/components/schemas/StaffRole"},"markets":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Markets"},"directions":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Directions"}},"additionalProperties":false,"type":"object","required":["email","role"],"title":"StaffInviteCreate","description":"Who to onboard, as what, over which slice of the business."},"StaffInviteIssued":{"properties":{"invite":{"$ref":"#/components/schemas/StaffInviteView"},"token":{"type":"string","title":"Token"}},"additionalProperties":false,"type":"object","required":["invite","token"],"title":"StaffInviteIssued","description":"The one and only response that ever contains the token.\n\nThe server stores just a hash, so this body cannot be reproduced later: the\nadmin copies the link now, or re-issues the invite."},"StaffInviteList":{"properties":{"items":{"items":{"$ref":"#/components/schemas/StaffInviteView"},"type":"array","title":"Items"},"total":{"type":"integer","title":"Total"},"limit":{"type":"integer","title":"Limit"},"offset":{"type":"integer","title":"Offset"}},"additionalProperties":false,"type":"object","required":["items","total","limit","offset"],"title":"StaffInviteList"},"StaffInviteView":{"properties":{"id":{"type":"string","title":"Id"},"email":{"type":"string","title":"Email"},"role":{"$ref":"#/components/schemas/StaffRole"},"scope":{"$ref":"#/components/schemas/StaffScope"},"status":{"type":"string","title":"Status"},"staff_id":{"type":"string","title":"Staff Id"},"tg_linked":{"type":"boolean","title":"Tg Linked","default":false},"invited_by":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Invited By"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"expires_at":{"type":"string","format":"date-time","title":"Expires At"},"accepted_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Accepted At"},"revoked_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Revoked At"}},"additionalProperties":false,"type":"object","required":["id","email","role","scope","status","staff_id","created_at","expires_at"],"title":"StaffInviteView","description":"An invite as the CRM list shows it. Carries no token — only its outcome."},"StaffList":{"properties":{"items":{"items":{"$ref":"#/components/schemas/StaffMemberView"},"type":"array","title":"Items"},"total":{"type":"integer","title":"Total"},"limit":{"type":"integer","title":"Limit"},"offset":{"type":"integer","title":"Offset"}},"additionalProperties":false,"type":"object","required":["items","total","limit","offset"],"title":"StaffList"},"StaffLoginRequest":{"properties":{"email":{"type":"string","title":"Email"},"password":{"type":"string","title":"Password"}},"additionalProperties":false,"type":"object","required":["email","password"],"title":"StaffLoginRequest"},"StaffLoginResponse":{"properties":{"totp_required":{"type":"boolean","title":"Totp Required"},"challenge_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Challenge Id"}},"additionalProperties":false,"type":"object","required":["totp_required"],"title":"StaffLoginResponse","description":"Password verified; a TOTP step usually follows (arch §10 checklist)."},"StaffMe":{"properties":{"tg_linked":{"type":"boolean","title":"Tg Linked"},"working_hours":{"$ref":"#/components/schemas/StaffWorkingHoursView"}},"additionalProperties":false,"type":"object","required":["tg_linked"],"title":"StaffMe","description":"The signed-in operator's own self-serve profile flags (slice B9c).\n\nDeliberately minimal and self-readable at the ``operator`` floor: the CRM's\noperator surface reads ``tg_linked`` from here so a non-admin operator can see\nwhether their Telegram is bound and drive link/unlink — without any admin-only\nroster. Only the boolean is exposed; the raw ``tg_user_id`` (a B5-redacted key)\nnever leaves the server."},"StaffMemberView":{"properties":{"id":{"type":"string","title":"Id"},"email":{"type":"string","title":"Email"},"name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Name"},"role":{"$ref":"#/components/schemas/StaffRole"},"scope":{"$ref":"#/components/schemas/StaffScope"},"active":{"type":"boolean","title":"Active"},"tg_linked":{"type":"boolean","title":"Tg Linked","default":false},"on_duty_now":{"type":"boolean","title":"On Duty Now","default":false},"working_hours":{"$ref":"#/components/schemas/StaffWorkingHoursView"}},"additionalProperties":false,"type":"object","required":["id","email","role","scope","active","working_hours"],"title":"StaffMemberView","description":"Оператор в админском ростере: кто он, чем владеет и на смене ли сейчас.\n\nПеречень полей — ровно то, что рисует ростер, и ничего сверх. Секретов и\nидентификаторов внешних систем здесь нет по построению: привязка мессенджера\nвидна булевым признаком, как и в описании приглашения."},"StaffRole":{"type":"string","enum":["operator","direction_lead","regional_admin","admin","superadmin"],"title":"StaffRole"},"StaffScope":{"properties":{"markets":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Markets"},"directions":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Directions"}},"additionalProperties":false,"type":"object","title":"StaffScope","description":"Effective scope = market ∩ direction; ``None`` = all (arch §7)."},"StaffSession":{"properties":{"id":{"type":"string","title":"Id"},"email":{"type":"string","title":"Email"},"name":{"type":"string","title":"Name"},"role":{"$ref":"#/components/schemas/StaffRole"},"scope":{"$ref":"#/components/schemas/StaffScope"}},"additionalProperties":false,"type":"object","required":["id","email","name","role","scope"],"title":"StaffSession"},"StaffSessionResponse":{"properties":{"staff":{"$ref":"#/components/schemas/StaffSession"}},"additionalProperties":false,"type":"object","required":["staff"],"title":"StaffSessionResponse"},"StaffTelegramLinkToken":{"properties":{"deeplink":{"type":"string","title":"Deeplink"},"expires_at":{"type":"string","format":"date-time","title":"Expires At"}},"additionalProperties":false,"type":"object","required":["deeplink","expires_at"],"title":"StaffTelegramLinkToken","description":"The one-time deep-link an operator taps to bind their Telegram account."},"StaffTotpRequest":{"properties":{"challenge_id":{"type":"string","title":"Challenge Id"},"code":{"type":"string","title":"Code"}},"additionalProperties":false,"type":"object","required":["challenge_id","code"],"title":"StaffTotpRequest"},"StaffWorkingHoursView":{"properties":{"days":{"items":{"$ref":"#/components/schemas/WorkingHoursDay"},"type":"array","title":"Days"}},"additionalProperties":false,"type":"object","title":"StaffWorkingHoursView","description":"Расписание сотрудника целиком, отсортированное по ``weekday``."},"StatusCheckView":{"properties":{"url":{"type":"string","title":"Url"},"keys":{"items":{"type":"string"},"type":"array","title":"Keys"}},"additionalProperties":false,"type":"object","required":["url"],"title":"StatusCheckView","description":"The destination portal's public status-tracking provider (slice A9).\n\n``url`` is the tracking page; ``keys`` is the ordered set of key names the\napplicant must re-enter there (per-country: a bare reference, or code+email+\nDOB, or reference+passport+DOB). Surfaced so the operator card / user status\npage can render a direct \"check status\" affordance."},"SubmitRequest":{"properties":{"purchase_mode":{"$ref":"#/components/schemas/PurchaseMode"},"consents":{"items":{"$ref":"#/components/schemas/ConsentAck"},"type":"array","title":"Consents"},"client_request_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Client Request Id"}},"additionalProperties":false,"type":"object","required":["purchase_mode"],"title":"SubmitRequest"},"SubmitResponse":{"properties":{"application":{"$ref":"#/components/schemas/ApplicationDetail"},"checkout_required":{"type":"boolean","title":"Checkout Required"}},"additionalProperties":false,"type":"object","required":["application","checkout_required"],"title":"SubmitResponse"},"SubscriptionCheckoutRequest":{"properties":{"market":{"type":"string","title":"Market"},"plan":{"type":"string","title":"Plan"},"client_request_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Client Request Id"}},"additionalProperties":false,"type":"object","required":["market","plan"],"title":"SubscriptionCheckoutRequest"},"SubscriptionCheckoutResponse":{"properties":{"subscription":{"$ref":"#/components/schemas/SubscriptionView"},"next_action":{"oneOf":[{"$ref":"#/components/schemas/NoAction"},{"$ref":"#/components/schemas/ClientSecretAction"},{"$ref":"#/components/schemas/RedirectAction"}],"title":"Next Action","discriminator":{"propertyName":"type","mapping":{"client_secret":"#/components/schemas/ClientSecretAction","none":"#/components/schemas/NoAction","redirect":"#/components/schemas/RedirectAction"}}}},"additionalProperties":false,"type":"object","required":["subscription","next_action"],"title":"SubscriptionCheckoutResponse"},"SubscriptionPlanDescriptor":{"properties":{"plan":{"type":"string","title":"Plan"},"price":{"$ref":"#/components/schemas/Money"},"trial_days":{"type":"integer","title":"Trial Days","default":0}},"additionalProperties":false,"type":"object","required":["plan","price"],"title":"SubscriptionPlanDescriptor"},"SubscriptionStatus":{"type":"string","enum":["trial","active","past_due","cancelled"],"title":"SubscriptionStatus"},"SubscriptionView":{"properties":{"market":{"type":"string","title":"Market"},"plan":{"type":"string","title":"Plan"},"status":{"$ref":"#/components/schemas/SubscriptionStatus"},"price":{"anyOf":[{"$ref":"#/components/schemas/Money"},{"type":"null"}]},"trial_ends_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Trial Ends At"},"current_period_end":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Current Period End"},"bonus_months":{"type":"integer","title":"Bonus Months","default":0}},"additionalProperties":false,"type":"object","required":["market","plan","status"],"title":"SubscriptionView"},"SupportInboxCounts":{"properties":{"total":{"type":"integer","title":"Total","default":0},"open":{"type":"integer","title":"Open","default":0},"pending_user":{"type":"integer","title":"Pending User","default":0},"unread":{"type":"integer","title":"Unread","default":0},"unassigned":{"type":"integer","title":"Unassigned","default":0}},"additionalProperties":false,"type":"object","title":"SupportInboxCounts","description":"Badge counters of the operator queue (grant ∩ ``market`` filter)."},"SupportMessageCreate":{"properties":{"body":{"type":"string","maxLength":8000,"minLength":1,"title":"Body"},"client_message_id":{"anyOf":[{"type":"string","maxLength":128,"minLength":1},{"type":"null"}],"title":"Client Message Id"}},"additionalProperties":false,"type":"object","required":["body"],"title":"SupportMessageCreate","description":"A message a client or an operator sends into a thread.\n\n``body`` is UNTRUSTED attacker-controlled text (see\n:mod:`trippy_core.db.models.support`): bounded here, escaped on render, kept\nout of logs and never spliced into an LLM prompt as instructions.\n\n``client_message_id`` is the caller's idempotency handle: retrying the same\nPOST with the same handle read-backs the stored message instead of posting a\nsecond one (partial UNIQUE per thread, SUP1)."},"SupportMessageList":{"properties":{"items":{"items":{"$ref":"#/components/schemas/SupportMessageView"},"type":"array","title":"Items"},"next_cursor":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Next Cursor"},"staff_read_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Staff Read At"}},"additionalProperties":false,"type":"object","required":["items"],"title":"SupportMessageList","description":"A keyset page of the transcript, oldest first — the polled endpoint.\n\n``staff_read_at`` rides along because it is the other half of the ETag: the\nvalidator is derived from ``(last public message id, staff_read_at)``, so\neverything the validator covers must be in the body and nothing else may be."},"SupportMessagePosted":{"properties":{"id":{"type":"string","title":"Id"},"thread_id":{"type":"string","title":"Thread Id"},"status":{"type":"string","title":"Status"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"duplicate":{"type":"boolean","title":"Duplicate","default":false}},"additionalProperties":false,"type":"object","required":["id","thread_id","status","created_at"],"title":"SupportMessagePosted","description":"Outcome of a send: the stored message and the thread's resulting status.\n\n``duplicate=True`` means this exact ``client_message_id`` was already stored —\nthe retry of a flaky POST returns the existing row (200) rather than posting a\nsecond message."},"SupportMessageView":{"properties":{"id":{"type":"string","title":"Id"},"author_type":{"type":"string","title":"Author Type"},"body":{"type":"string","title":"Body"},"created_at":{"type":"string","format":"date-time","title":"Created At"}},"additionalProperties":false,"type":"object","required":["id","author_type","body","created_at"],"title":"SupportMessageView","description":"One message in the client-visible transcript.\n\nThere is **no ``visibility`` field** — not \"internal messages are filtered\nout\", but \"this shape has no way to say internal\". The rows behind it come\nfrom the public-only repository SELECT (SUP2); the missing field is the second\nfence, so a future projection bug cannot render an internal note even if one\never reached this constructor."},"SupportPublicThread":{"properties":{"id":{"type":"string","title":"Id"},"status":{"type":"string","title":"Status"},"subject":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Subject"},"market_code":{"type":"string","title":"Market Code"},"application_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Application Id"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"last_message_at":{"type":"string","format":"date-time","title":"Last Message At"},"messages":{"items":{"$ref":"#/components/schemas/SupportMessageView"},"type":"array","title":"Messages"},"next_cursor":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Next Cursor"},"staff_read_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Staff Read At"}},"additionalProperties":false,"type":"object","required":["id","status","market_code","created_at","last_message_at"],"title":"SupportPublicThread","description":"What a guest reads back through his token: the thread **and** its transcript.\n\nExtends the client thread projection instead of wrapping it, so the fields SUP6\nalready served stay exactly where they were (the same reason the projection is a\nwhitelist: a column added to the table later is invisible until somebody adds it\non purpose).\n\nThe transcript is :class:`SupportMessageView` — the shape with **no\n``visibility`` field** — filled from the public-only SELECT. A guest is the one\nreader who reaches this data with no account behind him, and the operators'\ninternal notes about him are the most sensitive thing in the table, so the same\ntwo independent fences as the client path apply here and are pinned by a test."},"SupportThreadClaimResult":{"properties":{"thread_id":{"type":"string","title":"Thread Id"},"assignee_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Assignee Id"},"claimed":{"type":"boolean","title":"Claimed","default":false}},"additionalProperties":false,"type":"object","required":["thread_id"],"title":"SupportThreadClaimResult","description":"Outcome of the CAS on ``support_threads.assignee_id``."},"SupportThreadCreate":{"properties":{"market_code":{"type":"string","maxLength":16,"minLength":2,"title":"Market Code"},"body":{"type":"string","maxLength":8000,"minLength":1,"title":"Body"},"subject":{"anyOf":[{"type":"string","maxLength":200,"minLength":1},{"type":"null"}],"title":"Subject"},"application_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Application Id"},"contact_locale":{"anyOf":[{"type":"string","maxLength":8,"minLength":2},{"type":"null"}],"title":"Contact Locale"},"client_message_id":{"anyOf":[{"type":"string","maxLength":128,"minLength":1},{"type":"null"}],"title":"Client Message Id"}},"additionalProperties":false,"type":"object","required":["market_code","body"],"title":"SupportThreadCreate","description":"Open a thread from the account (ЛК, slice SUP4).\n\n``market_code`` is checked against the **active** markets by the handler (a\nthread in an unknown/retired market lands in nobody's queue), and\n``application_id`` — optional, a thread does not need a case — is checked for\nownership there too: accepting it on trust would let one client pin his\nconversation onto a stranger's case id, which every operator surface then\nrenders side by side with that stranger's data."},"SupportThreadList":{"properties":{"items":{"items":{"$ref":"#/components/schemas/SupportThreadView"},"type":"array","title":"Items"},"next_cursor":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Next Cursor"}},"additionalProperties":false,"type":"object","required":["items"],"title":"SupportThreadList","description":"One keyset page of the client's own threads (freshest activity first)."},"SupportThreadRead":{"properties":{"thread_id":{"type":"string","title":"Thread Id"},"staff_read_at":{"type":"string","format":"date-time","title":"Staff Read At"},"staff_read_by":{"type":"string","title":"Staff Read By"}},"additionalProperties":false,"type":"object","required":["thread_id","staff_read_at","staff_read_by"],"title":"SupportThreadRead","description":"Outcome of the read stamp — what the unread badge is computed from."},"SupportThreadStatusUpdate":{"properties":{"event":{"type":"string","enum":["resolve","close","spam","reopen"],"title":"Event"},"expected_status":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Expected Status"}},"additionalProperties":false,"type":"object","required":["event"],"title":"SupportThreadStatusUpdate","description":"Drive a thread-level operator event (SPEC-SUPPORT §5 table).\n\nThe vocabulary is an allow-list on the wire, so ``operator_message`` — a real\nevent, but one that must only ever fire as the side effect of *sending an\nanswer* — is not expressible here: a status route able to emit it would flip\nthe thread to \"мы ответили\" with no reply behind it.\n\n``expected_status`` is the caller's optimistic-concurrency handle: the CRM\nsends the status its screen was rendered from, and a thread somebody else\nmoved in the meantime answers 409 instead of applying a decision taken against\na stale card."},"SupportThreadView":{"properties":{"id":{"type":"string","title":"Id"},"status":{"type":"string","title":"Status"},"subject":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Subject"},"market_code":{"type":"string","title":"Market Code"},"application_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Application Id"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"last_message_at":{"type":"string","format":"date-time","title":"Last Message At"}},"additionalProperties":false,"type":"object","required":["id","status","market_code","created_at","last_message_at"],"title":"SupportThreadView","description":"A thread as the **client** sees it.\n\nDeliberately not the operator's row: no ``assignee_id``, no ``staff_read_by``,\nno SLA columns, no ``guest_token_hash``. The projection is a whitelist, so a\ncolumn added to the table later is invisible here until somebody adds it on\npurpose."},"TariffDescriptor":{"properties":{"key":{"type":"string","title":"Key"},"title":{"additionalProperties":{"type":"string"},"type":"object","title":"Title"},"summary":{"anyOf":[{"additionalProperties":{"type":"string"},"type":"object"},{"type":"null"}],"title":"Summary"},"includes":{"items":{"additionalProperties":{"type":"string"},"type":"object"},"type":"array","title":"Includes"},"service_fee":{"$ref":"#/components/schemas/ServiceFeeDescriptor"},"slot_filing":{"type":"boolean","title":"Slot Filing","default":false}},"additionalProperties":false,"type":"object","required":["key","service_fee"],"title":"TariffDescriptor","description":"One purchasable service format, as the choice card renders it (TS1)."},"TariffStage":{"properties":{"key":{"type":"string","title":"Key"},"title":{"additionalProperties":{"type":"string"},"type":"object","title":"Title"},"visible_if":{"anyOf":[{"$ref":"#/components/schemas/JsonValue"},{"type":"null"}]},"type":{"type":"string","const":"tariff","title":"Type"}},"additionalProperties":false,"type":"object","required":["key","type"],"title":"TariffStage","description":"The service-format choice, mounted immediately before payment (TS1).\n\nCarries no options of its own: the cards are rendered from\n``PricingDescriptor.tariffs``, so the price the applicant compares and the\nprice the checkout charges come from one place on the wire."},"TelegramLinkResponse":{"properties":{"code":{"type":"string","title":"Code"},"expires_at":{"type":"string","format":"date-time","title":"Expires At"}},"additionalProperties":false,"type":"object","required":["code","expires_at"],"title":"TelegramLinkResponse","description":"One-time code for the bot ``/link`` command (arch §7)."},"TimelineEvent":{"properties":{"id":{"type":"string","title":"Id"},"event":{"type":"string","title":"Event"},"from_status":{"anyOf":[{"$ref":"#/components/schemas/InternalStatus"},{"type":"null"}]},"to_status":{"anyOf":[{"$ref":"#/components/schemas/InternalStatus"},{"type":"null"}]},"actor_type":{"type":"string","enum":["applicant","operator","system"],"title":"Actor Type"},"actor_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Actor Id"},"reason":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Reason"},"created_at":{"type":"string","format":"date-time","title":"Created At"}},"additionalProperties":false,"type":"object","required":["id","event","actor_type","created_at"],"title":"TimelineEvent"},"TimelineResponse":{"properties":{"items":{"items":{"$ref":"#/components/schemas/TimelineEvent"},"type":"array","title":"Items"}},"additionalProperties":false,"type":"object","title":"TimelineResponse"},"TransitionRequest":{"properties":{"event":{"$ref":"#/components/schemas/OperatorEvent"},"reason":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Reason"},"per_applicant":{"items":{"$ref":"#/components/schemas/PerApplicantOutcome"},"type":"array","title":"Per Applicant"},"repin_direction_version":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Repin Direction Version"},"repin_market_profile_version":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Repin Market Profile Version"}},"additionalProperties":false,"type":"object","required":["event"],"title":"TransitionRequest"},"TravellerProfileList":{"properties":{"items":{"items":{"$ref":"#/components/schemas/TravellerProfileView"},"type":"array","title":"Items"}},"additionalProperties":false,"type":"object","title":"TravellerProfileList","description":"The owner's saved travellers, newest first."},"TravellerProfileView":{"properties":{"id":{"type":"string","title":"Id"},"full_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Full Name"},"citizenship":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Citizenship"},"answers":{"$ref":"#/components/schemas/JsonValue"},"sensitive":{"items":{"$ref":"#/components/schemas/SensitiveFieldMask"},"type":"array","title":"Sensitive"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"updated_at":{"type":"string","format":"date-time","title":"Updated At"}},"additionalProperties":false,"type":"object","required":["id","created_at","updated_at"],"title":"TravellerProfileView","description":"One saved traveller as the owner sees it — masks, never plaintext.\n\n``answers`` carries the non-sensitive roster subset the profile stored (name,\nbirth date, citizenship, passport *dates*); the passport *number* is\n``sensitive`` and lives only as ciphertext, so it surfaces here exactly as it\ndoes on a resumed draft: a :class:`SensitiveFieldMask` «•••• 1234». That is\nwhat lets the picker card show the traveller's passport is on file without\nanyone holding the §10 decrypt capability."},"UserProfileView":{"properties":{"first_name":{"type":"string","title":"First Name"},"last_name":{"type":"string","title":"Last Name"},"patronymic":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Patronymic"},"birth_date":{"type":"string","format":"date","title":"Birth Date"},"gender":{"type":"string","enum":["M","F"],"title":"Gender"},"citizenship":{"type":"string","title":"Citizenship"}},"additionalProperties":false,"type":"object","required":["first_name","last_name","birth_date","gender","citizenship"],"title":"UserProfileView","description":"The caller's own stored account profile; no owner selector or metadata."},"UserProfileWrite":{"properties":{"first_name":{"type":"string","title":"First Name"},"last_name":{"type":"string","title":"Last Name"},"patronymic":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Patronymic"},"birth_date":{"type":"string","format":"date","title":"Birth Date"},"gender":{"type":"string","enum":["M","F"],"title":"Gender"},"citizenship":{"type":"string","title":"Citizenship"}},"additionalProperties":false,"type":"object","required":["first_name","last_name","birth_date","gender","citizenship"],"title":"UserProfileWrite","description":"Complete replacement body for the signed-in applicant's own profile."},"UserTelegramLinkToken":{"properties":{"deeplink":{"type":"string","title":"Deeplink"},"expires_at":{"type":"string","format":"date-time","title":"Expires At"}},"additionalProperties":false,"type":"object","required":["deeplink","expires_at"],"title":"UserTelegramLinkToken","description":"The one-time deep-link an applicant taps to bind their Telegram account.\n\nDeliberately a separate type from the operator's ``StaffTelegramLinkToken``:\ndifferent audiences, different token tables and different TTL settings, so they\nmust stay distinct in the contract — a change on one side must not silently\nride out on the other."},"UserTelegramStatus":{"properties":{"linked":{"type":"boolean","title":"Linked"}},"additionalProperties":false,"type":"object","required":["linked"],"title":"UserTelegramStatus","description":"Whether this applicant's Telegram is bound — one boolean, nothing else.\n\nThe field set is deliberately closed to a single flag: the identifier of the\nbound Telegram channel is a delivery detail and must never cross the API\nboundary (APPLICANT-TG-NO-LEAK), not even as a masked or truncated form —\nwhich is also why its column name is absent from this contract. A\nsoft-deleted account reads ``linked=false`` (fail-closed), not an error.\n\nSeparate from the operator's ``StaffMe`` on purpose: different audiences and\ndifferent tables, so a field added for one must not ride out on the other."},"UtmLinkCreate":{"properties":{"title":{"type":"string","maxLength":200,"minLength":1,"title":"Title"},"target_url":{"type":"string","maxLength":2000,"minLength":1,"title":"Target Url"},"utm_source":{"type":"string","maxLength":100,"minLength":1,"title":"Utm Source"},"utm_medium":{"type":"string","maxLength":100,"minLength":1,"title":"Utm Medium"},"utm_campaign":{"type":"string","maxLength":200,"minLength":1,"title":"Utm Campaign"},"utm_content":{"anyOf":[{"type":"string","maxLength":200},{"type":"null"}],"title":"Utm Content"},"utm_term":{"anyOf":[{"type":"string","maxLength":200},{"type":"null"}],"title":"Utm Term"},"campaign_promo_code_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Campaign Promo Code Id"},"offer_expires_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Offer Expires At"}},"additionalProperties":false,"type":"object","required":["title","target_url","utm_source","utm_medium","utm_campaign"],"title":"UtmLinkCreate","description":"Что рекламируем (цель) и какими метками помечаем трафик."},"UtmLinkList":{"properties":{"items":{"items":{"$ref":"#/components/schemas/UtmLinkView"},"type":"array","title":"Items"},"total":{"type":"integer","title":"Total"}},"additionalProperties":false,"type":"object","required":["items","total"],"title":"UtmLinkList"},"UtmLinkView":{"properties":{"id":{"type":"string","title":"Id"},"code":{"type":"string","title":"Code"},"short_url":{"type":"string","title":"Short Url"},"title":{"type":"string","title":"Title"},"target_url":{"type":"string","title":"Target Url"},"utm_source":{"type":"string","title":"Utm Source"},"utm_medium":{"type":"string","title":"Utm Medium"},"utm_campaign":{"type":"string","title":"Utm Campaign"},"utm_content":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Utm Content"},"utm_term":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Utm Term"},"campaign_promo_code_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Campaign Promo Code Id"},"campaign_promo_code":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Campaign Promo Code"},"offer_expires_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Offer Expires At"},"clicks":{"type":"integer","title":"Clicks"},"registrations":{"type":"integer","title":"Registrations"},"payments":{"type":"integer","title":"Payments"},"offer_payments":{"type":"integer","title":"Offer Payments"},"offer_discount_totals":{"items":{"$ref":"#/components/schemas/Money"},"type":"array","title":"Offer Discount Totals"},"created_at":{"type":"string","format":"date-time","title":"Created At"}},"additionalProperties":false,"type":"object","required":["id","code","short_url","title","target_url","utm_source","utm_medium","utm_campaign","clicks","registrations","payments","offer_payments","offer_discount_totals","created_at"],"title":"UtmLinkView","description":"Ссылка, как её видит админка: короткий URL + воронка на сегодня."},"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"},"ValidationResultView":{"properties":{"ok":{"type":"boolean","title":"Ok"},"errors":{"items":{"$ref":"#/components/schemas/ConfigProblemView"},"type":"array","title":"Errors"},"warnings":{"items":{"$ref":"#/components/schemas/ConfigProblemView"},"type":"array","title":"Warnings"}},"additionalProperties":false,"type":"object","required":["ok"],"title":"ValidationResultView"},"ValidatorDescriptor":{"properties":{"rule":{"type":"string","title":"Rule"},"message":{"anyOf":[{"additionalProperties":{"type":"string"},"type":"object"},{"type":"null"}],"title":"Message"},"params":{"anyOf":[{"$ref":"#/components/schemas/JsonValue"},{"type":"null"}]}},"additionalProperties":false,"type":"object","required":["rule"],"title":"ValidatorDescriptor","description":"A validator surfaced to the client for instant, in-stage feedback.\n\n``rule`` names an L1 validator; ``params`` is opaque (the server remains the\nsource of truth — arch §3 client/server parity)."},"VersionBodyView":{"properties":{"layer":{"type":"string","title":"Layer"},"target":{"type":"string","title":"Target"},"version":{"type":"integer","title":"Version"},"payload":{"additionalProperties":true,"type":"object","title":"Payload"}},"additionalProperties":false,"type":"object","required":["layer","target","version"],"title":"VersionBodyView","description":"The config body (payload) of ONE immutable version (slice CFG15).\n\nA read-only projection of a single version's stored config body — the seam the\neditor uses to show the \"current config\" of a published/archived target before\nseeding a draft from it (there was only version *metadata* and a two-version\n*diff* before, no way to read one version's body). Status-agnostic: read by pin,\nso an archived version's body is served too. ``payload`` is version-free (the\nversion column is authoritative, ADR-10)."},"VersionDiffRequest":{"properties":{"from_version":{"type":"integer","title":"From Version"},"to_version":{"type":"integer","title":"To Version"}},"additionalProperties":false,"type":"object","required":["from_version","to_version"],"title":"VersionDiffRequest","description":"Pick the two immutable versions to diff (CFG11a)."},"VersionDiffView":{"properties":{"layer":{"type":"string","title":"Layer"},"target":{"type":"string","title":"Target"},"from_version":{"type":"integer","title":"From Version"},"to_version":{"type":"integer","title":"To Version"},"changes":{"items":{"additionalProperties":true,"type":"object"},"type":"array","title":"Changes"}},"additionalProperties":false,"type":"object","required":["layer","target","from_version","to_version"],"title":"VersionDiffView","description":"A structural diff between two versions' bodies (CFG11a).\n\n``changes`` is the SAME ``{path, before, after}`` shape :class:`FlowDiffView`\ncarries, so the CFG10 diff render consumes it unchanged — here over the raw\nconfig bodies of two versions rather than one draft's compiled flow."},"VersionHistoryView":{"properties":{"layer":{"type":"string","title":"Layer"},"target":{"type":"string","title":"Target"},"versions":{"items":{"$ref":"#/components/schemas/VersionRecordView"},"type":"array","title":"Versions"}},"additionalProperties":false,"type":"object","required":["layer","target"],"title":"VersionHistoryView"},"VersionPinView":{"properties":{"direction_slug":{"type":"string","title":"Direction Slug"},"direction_version":{"type":"integer","title":"Direction Version"},"market_code":{"type":"string","title":"Market Code"},"market_profile_version":{"type":"integer","title":"Market Profile Version"},"overlay_version":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Overlay Version"}},"additionalProperties":false,"type":"object","required":["direction_slug","direction_version","market_code","market_profile_version"],"title":"VersionPinView","description":"The two-axis pin echoed to clients (arch §3, §4)."},"VersionRecordView":{"properties":{"version":{"type":"integer","title":"Version"},"status":{"type":"string","title":"Status"},"published_by":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Published By"},"published_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Published At"}},"additionalProperties":false,"type":"object","required":["version","status"],"title":"VersionRecordView"},"VisaProcessingTimeDescriptor":{"properties":{"min_days":{"type":"integer","title":"Min Days"},"max_days":{"type":"integer","title":"Max Days"},"business_days":{"type":"boolean","title":"Business Days","default":false}},"additionalProperties":false,"type":"object","required":["min_days","max_days"],"title":"VisaProcessingTimeDescriptor"},"VisaProfileDescriptor":{"properties":{"processing_time":{"$ref":"#/components/schemas/VisaProcessingTimeDescriptor"},"validity_days":{"anyOf":[{"type":"integer"},{"type":"string","const":"authority_decides"}],"title":"Validity Days"},"max_stay_days":{"anyOf":[{"type":"integer"},{"type":"string","const":"authority_decides"}],"title":"Max Stay Days"},"entries":{"anyOf":[{"type":"integer"},{"type":"string","const":"authority_decides"}],"title":"Entries"}},"additionalProperties":false,"type":"object","required":["processing_time","validity_days","max_stay_days","entries"],"title":"VisaProfileDescriptor"},"WebhookAck":{"properties":{"received":{"type":"boolean","title":"Received","default":true}},"additionalProperties":false,"type":"object","title":"WebhookAck"},"WidgetConsentDescriptor":{"properties":{"kind":{"$ref":"#/components/schemas/ConsentKind"},"text_version":{"type":"string","title":"Text Version"},"text":{"type":"string","title":"Text"}},"additionalProperties":false,"type":"object","required":["kind","text_version","text"],"title":"WidgetConsentDescriptor","description":"The one consent the embed form must have ticked, already localized.\n\nUnlike :class:`~trippy_core.contracts.flow.ConsentDescriptor` this carries the\nresolved ``text`` for the requested locale: the embed page has no locale\ncatalogue of its own to look the wording up in."},"WidgetDescription":{"properties":{"market":{"type":"string","title":"Market"},"direction":{"type":"string","title":"Direction"},"locale":{"type":"string","title":"Locale"},"locales":{"items":{"type":"string"},"type":"array","title":"Locales"},"default_locale":{"type":"string","title":"Default Locale"},"fields":{"items":{"oneOf":[{"$ref":"#/components/schemas/ScalarFieldDescriptor"},{"$ref":"#/components/schemas/SelectFieldDescriptor"},{"$ref":"#/components/schemas/ListGroupFieldDescriptor"}],"discriminator":{"propertyName":"type","mapping":{"bool":"#/components/schemas/ScalarFieldDescriptor","country":"#/components/schemas/ScalarFieldDescriptor","date":"#/components/schemas/ScalarFieldDescriptor","email":"#/components/schemas/ScalarFieldDescriptor","file":"#/components/schemas/ScalarFieldDescriptor","gender":"#/components/schemas/ScalarFieldDescriptor","int":"#/components/schemas/ScalarFieldDescriptor","list_group":"#/components/schemas/ListGroupFieldDescriptor","multiselect":"#/components/schemas/SelectFieldDescriptor","number":"#/components/schemas/ScalarFieldDescriptor","passport":"#/components/schemas/ScalarFieldDescriptor","phone":"#/components/schemas/ScalarFieldDescriptor","select":"#/components/schemas/SelectFieldDescriptor","text":"#/components/schemas/ScalarFieldDescriptor","textarea":"#/components/schemas/ScalarFieldDescriptor"}}},"type":"array","title":"Fields"},"consent":{"$ref":"#/components/schemas/WidgetConsentDescriptor"}},"additionalProperties":false,"type":"object","required":["market","direction","locale","locales","default_locale","fields","consent"],"title":"WidgetDescription","description":"What the embed widget of a (market, direction) pair renders."},"WorkingHoursDay":{"properties":{"weekday":{"type":"integer","maximum":6.0,"minimum":0.0,"title":"Weekday"},"start":{"type":"string","pattern":"^([01][0-9]|2[0-3]):[0-5][0-9]$","title":"Start","examples":["09:00"]},"end":{"type":"string","pattern":"^([01][0-9]|2[0-3]):[0-5][0-9]$","title":"End","examples":["09:00"]}},"additionalProperties":false,"type":"object","required":["weekday","start","end"],"title":"WorkingHoursDay","description":"Один рабочий интервал одного дня недели, в зоне Europe/Moscow.\n\n``weekday`` — конвенция :meth:`datetime.date.weekday` (0=понедельник ..\n6=воскресенье), та же, что в колонке и в duty-сервисе. Правый край\nисключающий: ``start <= t < end``."},"WorkingHoursPut":{"properties":{"days":{"items":{"$ref":"#/components/schemas/WorkingHoursDay"},"type":"array","maxItems":7,"title":"Days"}},"additionalProperties":false,"type":"object","required":["days"],"title":"WorkingHoursPut","description":"Замена расписания целиком: что не прислали — того не будет.\n\nПустой список — легальный вход, он очищает расписание."}},"securitySchemes":{"ApiKeyBearer":{"type":"http","scheme":"bearer","bearerFormat":"vf_live_…","description":"A self-serve key from `POST /developers/api-keys`. Optional on the public read surface: with it the per-key quota applies instead of the per-IP one."}}},"tags":[{"name":"developers","description":"Self-serve API keys for the public read surface: issue (**public**, per-IP limited), inspect and revoke (**key-authenticated**, `Authorization: Bearer vf_live_…`). No human in the loop."},{"name":"health","description":"Liveness / readiness probes. **Public.** No session; not rate-limited; excluded from access logs."},{"name":"configs","description":"The published catalog: markets, locales, directions, and the compiled questionnaire (`FlowDescription`) a client renders. **Public** — no session, cacheable, per-IP rate-limited."},{"name":"landings","description":"Per-market landing-page and SEO content for a direction and locale. **Public**, cacheable, per-IP rate-limited."},{"name":"fx","description":"Display-only ECB reference exchange rates. **Public**, cached 5 min; rate-limited per IP."},{"name":"promotions","description":"Campaign offers: the anonymous link offer (**public**, cookie-driven) and the signed-in applicant's promo inventory (**applicant**)."},{"name":"analytics","description":"Client-side funnel event ingest. **Public** write endpoint; validate-and-accept (202), no read-back."},{"name":"support","description":"Support threads. `/support/public/*` is the **public** guest intake (per-IP and per-e-mail limits); `/support/threads*` is the signed-in **applicant's** own threads."},{"name":"embed","description":"Third-party widget hand-off: an anonymous embed starts a case (**public**, per-IP limited) that the **applicant** later claims."},{"name":"express","description":"Express checkout: pay first, fill in later. **Public** start and status endpoints, tied to the payment session rather than a user."},{"name":"auth","description":"Session lifecycle. Google OAuth and e-mail login links for applicants (**public** entry points that set the httpOnly cookie); password + TOTP login for **staff**; `/auth/session` and `/auth/logout` for any session."},{"name":"me","description":"The signed-in **applicant's** own profile and Telegram link."},{"name":"applications","description":"The **applicant's** visa cases: create, fill stages, write sensitive fields (encrypted at rest, returned masked), submit, resubmit, answer operator info-requests, delete. Owner-only."},{"name":"documents","description":"Document upload (presign → PUT to storage → confirm), preview, download and OCR prefill. **Applicant** for own cases; **staff (operator)** for the operator upload path."},{"name":"travellers","description":"Saved traveller profiles reused across cases, and document reuse from them. **Applicant**, owner-only."},{"name":"billing","description":"Payments: checkout, subscriptions, setup-intents, payment history (**applicant**, owner-only) and the PSP webhook (**public** entry, signature-verified)."},{"name":"referrals","description":"The **applicant's** referral code, stats and claim."},{"name":"operator","description":"The operator console: case queue, claim, timeline, checklist, FSM transitions, info-requests, support inbox, outbox dead-letters. **Staff**, minimum role `operator`; market-scoped."},{"name":"dossiers","description":"Generate the printable dossier of a case. **Staff**, role `operator`, market-scoped."},{"name":"staff","description":"The signed-in **staff** member's own identity and Telegram link (role `operator` or higher)."},{"name":"config-studio","description":"Authoring of the catalog layers (market profiles, directions, overlays): drafts, validation, preview, diff, publish, rollback, archive. **Staff**, minimum role `direction_lead`; direction-scoped."},{"name":"admin","description":"Staff invitations and activation. Activation endpoints are **public** (token-bearing); invite management is **staff** role `admin`."},{"name":"campaign-promos","description":"Campaign promo-code administration. **Staff**, role `admin`."},{"name":"utm-links","description":"Short tracking links: `/go/{code}` redirect is **public**; creating and listing links is **staff** role `admin`."}]}