{"components": {"headers": {"ETag": {"description": "strong entity tag \"v<version>\" of the project target set", "schema": {"pattern": "^\"v[0-9]+\"$", "type": "string"}}, "RetryAfter": {"schema": {"type": "integer"}}, "SourceETag": {"description": "strong entity tag \"s<version>\" of the project source locale; independent of the target-set ETag", "schema": {"pattern": "^\"s[0-9]+\"$", "type": "string"}}}, "parameters": {"Cursor": {"in": "query", "name": "cursor", "required": false, "schema": {"type": "string"}}, "IdempotencyKey": {"in": "header", "name": "Idempotency-Key", "required": false, "schema": {"maxLength": 200, "type": "string"}}, "IfMatch": {"description": "the strong ETag last read from GET /v1/projects/{project_id}/locales; absent or \"*\" is 428, any other mismatch 412", "in": "header", "name": "If-Match", "required": true, "schema": {"type": "string"}}, "IfMatchSource": {"description": "the strong source ETag \"s<n>\" last read from GET /v1/projects/{project_id}/source-locale; absent or \"*\" is 428, any other value (stale, weak, or a locales ETag) 412", "in": "header", "name": "If-Match", "required": true, "schema": {"type": "string"}}, "Limit": {"in": "query", "name": "limit", "required": false, "schema": {"default": 50, "maximum": 500, "minimum": 1, "type": "integer"}}, "ProjectId": {"in": "path", "name": "project_id", "required": true, "schema": {"format": "uuid", "type": "string"}}, "TenantHeader": {"in": "header", "name": "L10n-Tenant", "required": false, "schema": {"format": "uuid", "type": "string"}}}, "responses": {"LanguageCoreUnavailable": {"content": {"application/problem+json": {"schema": {"$ref": "#/components/schemas/Problem"}}}, "description": "language_core_unavailable - a process pin check is not ok (LS6); nothing was changed", "headers": {"Retry-After": {"$ref": "#/components/headers/RetryAfter"}}}, "PreconditionFailed": {"content": {"application/problem+json": {"schema": {"$ref": "#/components/schemas/Problem"}}}, "description": "precondition_failed - If-Match does not strongly match the current ETag; nothing was changed", "headers": {"ETag": {"$ref": "#/components/headers/ETag"}}}, "PreconditionRequired": {"content": {"application/problem+json": {"schema": {"$ref": "#/components/schemas/Problem"}}}, "description": "precondition_required - If-Match is absent or \"*\""}, "SourcePreconditionFailed": {"content": {"application/problem+json": {"schema": {"$ref": "#/components/schemas/Problem"}}}, "description": "precondition_failed - If-Match does not strongly match the current source ETag; nothing was changed", "headers": {"ETag": {"$ref": "#/components/headers/SourceETag"}}}}, "schemas": {"Annotations": {"properties": {"blocking_failed": {"items": {"type": "string"}, "type": "array"}, "checks": {"description": "every check's result; empty when untranslated", "items": {"$ref": "#/components/schemas/QaCheck"}, "type": "array"}, "key_id": {"format": "uuid", "type": "string"}, "locale": {"type": "string"}, "revision": {"minimum": 0, "type": "integer"}, "state": {"type": "string"}}, "required": ["key_id", "locale", "state", "revision", "checks", "blocking_failed"], "type": "object"}, "ApiKeyCreate": {"additionalProperties": false, "properties": {"label": {"maxLength": 64, "type": "string"}}, "type": "object"}, "ApiKeyIssued": {"properties": {"api_key": {"description": "shown exactly once", "type": "string"}, "key_id": {"type": "string"}}, "required": ["key_id", "api_key"], "type": "object"}, "AuditEvent": {"properties": {"action": {"type": "string"}, "actor_id": {"type": "string"}, "actor_kind": {"type": "string"}, "event_id": {"type": "string"}, "hash": {"type": "string"}, "occurred_at": {"type": "string"}, "payload": {"type": "object"}, "prev_hash": {"type": "string"}, "request_id": {"type": "string"}, "seq": {"type": "integer"}, "target_id": {"type": "string"}, "target_type": {"type": "string"}}, "required": ["seq", "event_id", "occurred_at", "actor_kind", "actor_id", "action", "target_type", "target_id", "hash", "prev_hash"], "type": "object"}, "AuditVerify": {"properties": {"anchors_verified": {"type": "integer"}, "breaks": {"items": {"type": "object"}, "type": "array"}, "checkpoints_verified": {"type": "integer"}, "events": {"type": "integer"}, "head_seq": {"type": "integer"}, "residual_tail": {"type": "integer"}}, "required": ["events", "breaks", "checkpoints_verified", "anchors_verified", "head_seq"], "type": "object"}, "FileImported": {"properties": {"file_id": {"format": "uuid", "type": "string"}, "format": {"enum": ["json", "yaml", "properties"], "type": "string"}, "keys": {"minimum": 0, "type": "integer"}, "path": {"type": "string"}, "profile": {"description": "yaml only", "enum": ["rails", "flat"], "type": "string"}, "project_id": {"format": "uuid", "type": "string"}, "root_locale": {"description": "rails profile only", "type": "string"}, "sha256": {"pattern": "^[0-9a-f]{64}$", "type": "string"}, "size": {"maximum": 52428800, "minimum": 0, "type": "integer"}, "version": {"minimum": 1, "type": "integer"}}, "required": ["file_id", "project_id", "path", "format", "version", "sha256", "size", "keys"], "type": "object"}, "Grant": {"properties": {"id": {"format": "uuid", "type": "string"}, "intents": {"items": {"properties": {"permissions": {"items": {"type": "string"}, "type": "array"}, "state": {"enum": ["granting", "granted", "revoking", "revoked"]}, "target": {"type": "string"}}, "required": ["target", "permissions", "state"], "type": "object"}, "type": "array"}, "permissions": {"description": "conferred now - the expansion of intents in state granted only, as the access path admits them; granting, revoking and revoked intents confer nothing (#54, F54)", "items": {"type": "string"}, "type": "array"}, "principal_id": {"format": "uuid", "type": "string"}, "scope": {"type": "object"}, "state": {"description": "degraded - the record is active but some or all intents were removed in auth and are not re-granted (#54)", "enum": ["granting", "granted", "revoking", "revoked", "degraded"]}, "template": {"type": "string"}}, "required": ["id", "principal_id", "template", "scope", "permissions", "state", "intents"], "type": "object"}, "GrantCreate": {"additionalProperties": false, "properties": {"principal_id": {"format": "uuid", "type": "string"}, "scope": {"additionalProperties": false, "properties": {"locale": {"maxLength": 40, "type": "string"}, "project_id": {"format": "uuid", "type": "string"}, "workspace_id": {"format": "uuid", "type": "string"}}, "type": "object"}, "template": {"enum": ["org_admin", "org_auditor", "org_billing", "member_manager", "ws_admin", "project_manager", "viewer", "translator", "reviewer", "approver", "ci", "push", "build", "publisher", "runner", "terminologist"]}}, "required": ["principal_id", "template"], "type": "object"}, "GrantList": {"properties": {"grants": {"items": {"$ref": "#/components/schemas/Grant"}, "type": "array"}}, "required": ["grants"], "type": "object"}, "KeyPage": {"properties": {"keys": {"items": {"$ref": "#/components/schemas/StringKey"}, "type": "array"}, "next_cursor": {"type": ["string", "null"]}}, "required": ["keys", "next_cursor"], "type": "object"}, "LocaleList": {"properties": {"cldr_version": {"type": "string"}, "locales": {"items": {"$ref": "#/components/schemas/LocaleProfile"}, "type": "array"}}, "required": ["cldr_version", "locales"], "type": "object"}, "LocaleProfile": {"properties": {"cardinal": {"items": {"enum": ["zero", "one", "two", "few", "many", "other"]}, "type": "array"}, "direction": {"enum": ["ltr", "rtl"]}, "identity_key": {"type": "string"}, "ordinal": {"items": {"enum": ["zero", "one", "two", "few", "many", "other"]}, "type": "array"}, "reviewed": {"type": "boolean"}, "tag": {"type": "string"}}, "required": ["tag", "identity_key", "reviewed", "direction", "cardinal", "ordinal"], "type": "object"}, "LocaleValidate": {"additionalProperties": false, "properties": {"tag": {"maxLength": 64, "minLength": 1, "type": "string"}}, "required": ["tag"], "type": "object"}, "LocaleValidation": {"oneOf": [{"additionalProperties": false, "properties": {"canonical": {"type": "string"}, "content_form": {"type": "string"}, "identity_key": {"type": "string"}, "valid": {"const": true}}, "required": ["valid", "canonical", "content_form", "identity_key"], "type": "object"}, {"additionalProperties": false, "properties": {"code": {"enum": ["locale_ill_formed", "locale_invalid_subtag", "locale_deprecated_no_replacement", "locale_not_translatable", "locale_script_required", "locale_too_long"]}, "valid": {"const": false}}, "required": ["valid", "code"], "type": "object"}]}, "Me": {"properties": {"kind": {"enum": ["member", "service_account"]}, "permissions": {"items": {"type": "string"}, "type": "array"}, "principal_id": {"format": "uuid", "type": "string"}, "tenant_id": {"format": "uuid", "type": "string"}}, "required": ["tenant_id", "principal_id", "kind", "permissions"], "type": "object"}, "Member": {"properties": {"created_at": {"format": "date-time", "type": "string"}, "display_name": {"type": "string"}, "email": {"type": "string"}, "id": {"format": "uuid", "type": "string"}, "short_id": {"type": "string"}, "status": {"enum": ["pending", "active", "removed", "erased"]}}, "required": ["id", "short_id", "status", "created_at"], "type": "object"}, "MemberCreate": {"additionalProperties": false, "properties": {"display_name": {"maxLength": 200, "type": "string"}, "email": {"format": "email", "maxLength": 254, "type": "string"}}, "required": ["email"], "type": "object"}, "Problem": {"properties": {"code": {"type": "string"}, "detail": {"type": "string"}, "field": {"type": "string"}, "status": {"type": "integer"}, "title": {"type": "string"}, "type": {"type": "string"}}, "required": ["type", "title", "status", "code"], "type": "object"}, "Project": {"properties": {"created_at": {"format": "date-time", "type": "string"}, "id": {"format": "uuid", "type": "string"}, "legal_prefixes": {"description": "key-name prefixes whose translations an approval request never batches (legal text needs its own sign-off); set by manage via PATCH", "items": {"type": "string"}, "type": "array"}, "name": {"type": "string"}, "short_id": {"type": "string"}, "slug": {"type": "string"}, "source_locale": {"type": "string"}, "workspace_id": {"format": "uuid", "type": "string"}}, "required": ["id", "short_id", "workspace_id", "slug", "name", "source_locale", "created_at"], "type": "object"}, "ProjectCreate": {"additionalProperties": false, "properties": {"name": {"maxLength": 200, "type": "string"}, "slug": {"pattern": "^[a-z0-9][a-z0-9-]{1,62}$", "type": "string"}, "source_locale": {"description": "stored as its content form; a content form longer than 64 is 422 locale_too_long (SPEC 0052)", "maxLength": 64, "minLength": 2, "type": "string"}, "workspace_id": {"format": "uuid", "type": "string"}}, "required": ["workspace_id", "slug", "name", "source_locale"], "type": "object"}, "ProjectLocales": {"properties": {"locales": {"description": "ordered by identity_key; tag is the stored content form", "items": {"properties": {"identity_key": {"type": "string"}, "tag": {"type": "string"}}, "required": ["tag", "identity_key"], "type": "object"}, "type": "array"}, "project_id": {"format": "uuid", "type": "string"}, "version": {"minimum": 0, "type": "integer"}}, "required": ["project_id", "version", "locales"], "type": "object"}, "ProjectLocalesPut": {"additionalProperties": false, "properties": {"locales": {"items": {"maxLength": 64, "minLength": 1, "type": "string"}, "maxItems": 100, "minItems": 1, "type": "array"}}, "required": ["locales"], "type": "object"}, "ProjectPatch": {"additionalProperties": false, "properties": {"legal_prefixes": {"items": {"maxLength": 200, "minLength": 1, "type": "string"}, "maxItems": 50, "type": "array"}, "name": {"maxLength": 200, "type": "string"}}, "type": "object"}, "QaCheck": {"properties": {"blocking": {"description": "true for Q01-Q05, Q10 (do_not_translate: a project DNT term in the source missing verbatim from the target), Q11 (forbidden_term: a project forbidden term in the target), Q16, Q17, Q18 (invisible_characters: a TAG character U+E0000-U+E007F, a bidi embedding, override or isolate U+202A-U+202E or U+2066-U+2069, LRM, RLM or ALM, ZWSP, WJ or BOM, or a C0/C1 control or DEL other than tab, LF and CR, in the target but not in the source; detail lists them as U+XXXX); a failed blocking check refuses review and approve", "type": "boolean"}, "check": {"enum": ["Q01", "Q02", "Q03", "Q04", "Q05", "Q06", "Q07", "Q08", "Q09", "Q10", "Q11", "Q12", "Q14", "Q16", "Q17", "Q18", "Q19", "Q20"], "type": "string"}, "detail": {"type": ["string", "null"]}, "name": {"type": "string"}, "status": {"enum": ["pass", "fail", "skipped"], "type": "string"}}, "required": ["check", "name", "blocking", "status", "detail"], "type": "object"}, "ServiceAccount": {"properties": {"created_at": {"format": "date-time", "type": "string"}, "id": {"format": "uuid", "type": "string"}, "label": {"type": "string"}, "short_id": {"type": "string"}, "status": {"enum": ["active", "revoked"]}}, "required": ["id", "short_id", "label", "status", "created_at"], "type": "object"}, "ServiceAccountCreate": {"additionalProperties": false, "properties": {"label": {"maxLength": 64, "type": "string"}}, "required": ["label"], "type": "object"}, "Settings": {"additionalProperties": false, "properties": {"display_name": {"maxLength": 200, "type": "string"}, "legal_approval_quorum": {"minimum": 1, "type": "integer"}}, "type": "object"}, "SourceLocale": {"properties": {"identity_key": {"type": "string"}, "project_id": {"format": "uuid", "type": "string"}, "source_locale": {"description": "stored content form", "type": "string"}, "version": {"minimum": 0, "type": "integer"}}, "required": ["project_id", "source_locale", "identity_key", "version"], "type": "object"}, "SourceLocalePut": {"additionalProperties": false, "properties": {"source_locale": {"maxLength": 64, "minLength": 1, "type": "string"}}, "required": ["source_locale"], "type": "object"}, "StringKey": {"properties": {"file_id": {"format": "uuid", "type": "string"}, "forms": {"additionalProperties": {"type": "string"}, "description": "CLDR category to source text of a plural; null for a text unit", "type": ["object", "null"]}, "id": {"format": "uuid", "type": "string"}, "path": {"type": "string"}, "plural": {"type": "boolean"}, "source": {"description": "the source text of a text unit; null for a plural", "type": ["string", "null"]}}, "required": ["id", "file_id", "path", "plural", "source", "forms"], "type": "object"}, "Translation": {"properties": {"forms": {"additionalProperties": {"type": "string"}, "type": ["object", "null"]}, "key_id": {"format": "uuid", "type": "string"}, "locale": {"description": "canonical BCP 47 tag", "maxLength": 40, "type": "string"}, "origin": {"description": "who wrote the current text: ai (AI translate), human (PUT), import (XLIFF), null (written before origin was recorded)", "enum": ["ai", "human", "import", null], "type": ["string", "null"]}, "revision": {"description": "0 when untranslated", "minimum": 0, "type": "integer"}, "state": {"enum": ["untranslated", "draft", "needs_attention", "reviewed", "approved", "outdated"], "type": "string"}, "text": {"type": ["string", "null"]}, "updated_at": {"format": "date-time", "type": ["string", "null"]}, "updated_by": {"format": "uuid", "type": ["string", "null"]}}, "required": ["key_id", "locale", "state", "revision", "text", "forms", "updated_by", "updated_at"], "type": "object"}, "TranslationPage": {"properties": {"next_cursor": {"type": ["string", "null"]}, "translations": {"items": {"$ref": "#/components/schemas/Translation"}, "type": "array"}}, "required": ["translations", "next_cursor"], "type": "object"}, "TranslationPut": {"additionalProperties": false, "description": "exactly one of text (a text unit) or forms (a plural; exactly the source key's CLDR categories)", "properties": {"forms": {"additionalProperties": {"maxLength": 262144, "type": "string"}, "maxProperties": 6, "minProperties": 1, "type": "object"}, "text": {"description": "at most 65536 bytes of UTF-8, else 422 value_too_long", "maxLength": 262144, "type": "string"}}, "type": "object"}, "TranslationTransition": {"additionalProperties": false, "description": "WF1: review draft->reviewed, reject reviewed->draft, approve reviewed->approved, revoke approved->reviewed", "properties": {"action": {"enum": ["review", "reject", "approve", "revoke"], "type": "string"}, "revision": {"description": "when given it must equal the stored revision (409 revision_mismatch)", "minimum": 1, "type": "integer"}}, "required": ["action"], "type": "object"}, "Workspace": {"properties": {"created_at": {"format": "date-time", "type": "string"}, "id": {"format": "uuid", "type": "string"}, "name": {"type": "string"}, "short_id": {"type": "string"}, "slug": {"type": "string"}}, "required": ["id", "short_id", "slug", "name", "created_at"], "type": "object"}, "WorkspaceCreate": {"additionalProperties": false, "properties": {"name": {"maxLength": 200, "type": "string"}, "slug": {"pattern": "^[a-z0-9][a-z0-9-]{1,62}$", "type": "string"}}, "required": ["slug", "name"], "type": "object"}}, "securitySchemes": {"apiKey": {"scheme": "bearer", "type": "http"}, "session": {"in": "cookie", "name": "__Host-l10n_session", "type": "apiKey"}}}, "info": {"description": "Contract for phase P1 (security spine). Every route below is enumerated by probe_tenant_isolation (TN1)\nand must match the router exactly (parity test). x-permission names the permission required (plan D8);\nx-phase the phase that introduced the route. Errors are RFC 9457 application/problem+json with a stable\n`code`; bodies never echo input; unknown fields and query parameters are 400 naming them (OP4).\nMachine credentials: `Authorization: Bearer rak_...` plus `L10n-Tenant: <uuid>` (selects the auth namespace).\nHuman credentials: cookie `__Host-l10n_session` (bound to one tenant; `L10n-Tenant`, if sent, may only confirm).\n", "title": "RODMENA L10n API", "version": "0.1.0-p1", "x-core-semantics": "`x-core: true` marks an operation whose every request variant needs the language core (LS6). While any of the ten\nprocess pin checks is not `ok`, such a variant answers 503 `language_core_unavailable`\n(`components.responses.LanguageCoreUnavailable`) and changes nothing; every other operation keeps working.\n`x-core-when` narrows the marker to requests whose body matches the stated condition.\n"}, "openapi": "3.1.0", "paths": {"/auth/backchannel-logout": {"post": {"requestBody": {"content": {"application/x-www-form-urlencoded": {"schema": {"properties": {"logout_token": {"type": "string"}}, "required": ["logout_token"], "type": "object"}}}}, "responses": {"200": {"description": "{\"acted\": bool, \"revoked\": int}"}, "400": {"description": "unverifiable token"}}, "x-idempotency": "exempt", "x-permission": "logout_token", "x-phase": "P1"}}, "/auth/callback": {"get": {"parameters": [{"in": "query", "name": "code", "required": false, "schema": {"type": "string"}}, {"in": "query", "name": "state", "required": false, "schema": {"type": "string"}}, {"in": "query", "name": "error", "required": false, "schema": {"type": "string"}}, {"in": "query", "name": "error_description", "required": false, "schema": {"type": "string"}}, {"in": "query", "name": "iss", "required": false, "schema": {"type": "string"}}], "responses": {"200": {"description": "session established; cookie set"}, "401": {"binding or ID token verification failed (uniform body)": null, "description": "state"}, "403": {"description": "signed in but not a member of the tenant"}}, "x-permission": "none", "x-phase": "P1"}}, "/auth/logged-out": {"get": {"responses": {"200": {"description": "landing page after RP-initiated logout"}}, "x-permission": "none", "x-phase": "P1"}}, "/auth/login": {"get": {"parameters": [{"in": "query", "name": "tenant", "required": true, "schema": {"format": "uuid", "type": "string"}}], "responses": {"302": {"description": "redirect to RODMENA ID with PKCE S256", "state and nonce; sealed login-state cookie set": null}, "400": {"description": "tenant missing or not a UUID"}, "404": {"description": "tenant not found or inactive"}}, "x-permission": "none", "x-phase": "P1"}}, "/auth/logout": {"post": {"responses": {"200": {"description": "session ended; body carries the identity logout URL"}}, "x-permission": "session", "x-phase": "P1"}}, "/auth/switch-tenant": {"post": {"requestBody": {"content": {"application/json": {"schema": {"additionalProperties": false, "properties": {"tenant_id": {"format": "uuid", "type": "string"}}, "required": ["tenant_id"], "type": "object"}}}}, "responses": {"200": {"description": "new session bound to the chosen tenant"}, "403": {"description": "not a member"}}, "x-permission": "session", "x-phase": "P1"}}, "/healthz": {"get": {"responses": {"200": {"description": "alive"}}, "x-permission": "none", "x-phase": "P0"}, "head": {"responses": {"200": {"description": "alive"}}, "x-permission": "none", "x-phase": "P0"}}, "/llms.txt": {"get": {"responses": {"200": {"content": {"text/plain": {"schema": {"type": "string"}}}, "description": "the agent integration guide (docs/api/llms.txt): base URL, authentication, the website flow, errors, limits and pagination; every route it names is in this contract"}}, "x-permission": "none", "x-phase": "P5"}, "head": {"responses": {"200": {"description": "the GET status and headers with an empty body"}}, "x-permission": "none", "x-phase": "P5"}}, "/openapi.json": {"get": {"responses": {"200": {"content": {"application/json": {"schema": {"type": "object"}}}, "description": "this contract (docs/api/openapi.yaml) as JSON; ETag is the quoted SHA-256 hex of the body"}}, "x-permission": "none", "x-phase": "P5"}, "head": {"responses": {"200": {"description": "the GET status and headers with an empty body"}}, "x-permission": "none", "x-phase": "P5"}}, "/readyz": {"get": {"responses": {"200": {"description": "ready"}, "503": {"description": "not ready"}}, "x-permission": "none", "x-phase": "P1"}, "head": {"responses": {"200": {"description": "ready"}, "503": {"description": "not ready"}}, "x-permission": "none", "x-phase": "P1"}}, "/v1/approval-requests/{request_id}": {"get": {"parameters": [{"in": "path", "name": "request_id", "required": true, "schema": {"format": "uuid", "type": "string"}}], "responses": {"200": {"description": "the request with status (submitting, pending, approved, rejected, changes_requested, timed_out, cancelled, failed), decided_by (the Futex actor), reason, decided_at, review_url, context (the exact canonical JSON text sent to Futex as the decision context: keys sorted, separators , and :, UTF-8 not escaped), futex_context_hash (the context_hash Futex returned; see SPECS/0135 for the hash rule) and items [{key (the key path), key_id, path, revision, targetHash, outcome: null, approved, returned, skipped_changed or unchanged}]"}, "403": {"description": "forbidden"}, "404": {"description": "not found in this tenant"}}, "x-permission": "p_<p>_read", "x-phase": "P7"}}, "/v1/audit/events": {"get": {"parameters": [{"in": "query", "name": "after_seq", "required": false, "schema": {"type": "integer"}}, {"$ref": "#/components/parameters/Limit"}], "responses": {"200": {"description": "events in seq order"}}, "x-permission": "org_audit-read", "x-phase": "P1"}}, "/v1/audit/export": {"get": {"parameters": [{"in": "query", "name": "format", "required": true, "schema": {"enum": ["ndjson", "cef"]}}, {"in": "query", "name": "from_seq", "required": false, "schema": {"type": "integer"}}, {"in": "query", "name": "to_seq", "required": false, "schema": {"type": "integer"}}], "responses": {"200": {"description": "events plus checkpoints for the range"}}, "x-permission": "org_audit-read", "x-phase": "P1"}}, "/v1/audit/verify": {"get": {"responses": {"200": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/AuditVerify"}}}}}, "x-permission": "org_audit-read", "x-phase": "P1"}}, "/v1/futex/webhook": {"post": {"responses": {"200": {"description": "the decision was re-read from Futex and settled (approved: items whose stored target still matches targetHash and revision move to approved with approval_ref and approved_by_label; rejected or changes_requested: items return to draft with the reason; timed_out or cancelled: nothing changes), or the event was a duplicate (X-HITL-Event-Id), or the decision is not ours"}, "401": {"description": "signature_invalid, timestamp_invalid or timestamp_stale (more than 300 s off); nothing changes"}, "413": {"description": "payload_too_large"}, "503": {"description": "retry: the secret is unset, or Futex could not be read; nothing changes and the event is not recorded, so Futex retries it"}}, "x-permission": "none (X-HITL-Signature = sha256= HMAC-SHA256(FUTEX_WEBHOOK_SECRET, X-HITL-Timestamp + '.' + raw body))", "x-phase": "P7"}}, "/v1/grants": {"get": {"responses": {"200": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/GrantList"}}}, "description": "every grant in the tenant, each a Grant view (#54)"}}, "x-permission": "org_members-manage", "x-phase": "P1"}, "post": {"parameters": [{"$ref": "#/components/parameters/IdempotencyKey"}], "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/GrantCreate"}}}}, "responses": {"201": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/Grant"}}}}, "403": {"description": "outside the grantor's own permissions"}}, "x-permission": "org_members-manage | ws admin, bounded by grantor (AZ9)", "x-phase": "P1"}}, "/v1/grants/{grant_id}": {"delete": {"parameters": [{"in": "path", "name": "grant_id", "required": true, "schema": {"format": "uuid", "type": "string"}}], "responses": {"200": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/Grant"}}}, "description": "the Grant view after the revoke. state is revoked when auth removed every intent at commit; it is revoking when auth was unavailable (outage, 503, egress pacing) - the record is revoked, the intents stay revoking until reconcile removes them in auth, and permissions is [] because access already admits only granted intents (#54, F54)"}}, "x-permission": "org_members-manage | ws admin, bounded", "x-phase": "P1"}}, "/v1/keys/{key_id}/annotations/{locale}": {"get": {"parameters": [{"in": "path", "name": "key_id", "required": true, "schema": {"format": "uuid", "type": "string"}}, {"in": "path", "name": "locale", "required": true, "schema": {"type": "string"}}], "responses": {"200": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/Annotations"}}}, "description": "every QA check's result on the stored translation"}, "403": {"description": "forbidden"}, "404": {"description": "not found in this tenant"}, "422": {"description": "an LS1 tag refusal code or locale_too_long"}, "503": {"$ref": "#/components/responses/LanguageCoreUnavailable"}}, "x-permission": "p_<p>_read", "x-phase": "P4"}}, "/v1/keys/{key_id}/suggestions/{locale}": {"get": {"parameters": [{"in": "path", "name": "key_id", "required": true, "schema": {"format": "uuid", "type": "string"}}, {"in": "path", "name": "locale", "required": true, "schema": {"type": "string"}}], "responses": {"200": {"content": {"application/json": {"schema": {"type": "object"}}}, "description": "translation-memory suggestions for the key source (a plural key queries its other form) into the canonical locale, from this tenant only (RLS): exact matches (score 100, match exact) first, then fuzzy matches with rapidfuzz ratio >= 75 among pg_trgm candidates (match fuzzy), at most 5; each {score, match, source, target, origin}; the TM holds one unit per (source text, source locale, target locale), written on approval"}, "403": {"description": "forbidden"}, "404": {"description": "not found in this tenant"}, "422": {"description": "an LS1 tag refusal code or locale_too_long"}, "503": {"$ref": "#/components/responses/LanguageCoreUnavailable"}}, "x-core": true, "x-permission": "p_<p>_read", "x-phase": "P5"}}, "/v1/keys/{key_id}/translations/{locale}": {"get": {"parameters": [{"in": "path", "name": "key_id", "required": true, "schema": {"format": "uuid", "type": "string"}}, {"in": "path", "name": "locale", "required": true, "schema": {"type": "string"}}], "responses": {"200": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/Translation"}}}, "description": "the translation in the canonical locale", "or state untranslated with revision 0": null}, "403": {"description": "forbidden"}, "404": {"description": "not found in this tenant"}, "422": {"description": "an LS1 tag refusal code or locale_too_long (canonical form over 40)"}, "503": {"$ref": "#/components/responses/LanguageCoreUnavailable"}}, "x-core": true, "x-permission": "p_<p>_read", "x-phase": "P3"}, "put": {"parameters": [{"in": "path", "name": "key_id", "required": true, "schema": {"format": "uuid", "type": "string"}}, {"in": "path", "name": "locale", "required": true, "schema": {"type": "string"}}, {"in": "header", "name": "Idempotency-Key", "required": true, "schema": {"maxLength": 200, "type": "string"}}], "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/TranslationPut"}}}, "required": true}, "responses": {"200": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/Translation"}}}, "description": "text replaced", "state draft (WF1 outdated->draft on translate; a reviewed or approved translation returns to draft); revision +1; exactly one translation audit event": null}, "201": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/Translation"}}}, "description": "untranslated to draft (WF1); revision 1; exactly one translation audit event"}, "400": {"description": "unknown_field, invalid_field, malformed_json, invalid_body or invalid_parameter (missing Idempotency-Key)"}, "403": {"description": "forbidden"}, "404": {"description": "not found in this tenant"}, "409": {"description": "key_obsolete (the key is absent from the latest source version) or idempotency_in_progress"}, "415": {"description": "unsupported_media_type"}, "422": {"description": "an LS1 tag refusal code, locale_too_long, edit_shape (text vs forms, or categories differ from the target locale's CLDR cardinal categories), value_too_long (a value over 65536 bytes of UTF-8), value_unrepresentable, or idempotency_key_reused"}, "503": {"$ref": "#/components/responses/LanguageCoreUnavailable"}}, "x-core": true, "x-order": "authn, Idempotency-Key absent (400), body shape (400), replay, tag (422), key (404), authz on the canonical tag (403), shape against the key (422), transition (409)", "x-permission": "p_<p>_loc_<tag>_translate (or p_<p>_anyloc_translate)", "x-phase": "P3"}}, "/v1/keys/{key_id}/translations/{locale}/transition": {"post": {"parameters": [{"in": "path", "name": "key_id", "required": true, "schema": {"format": "uuid", "type": "string"}}, {"in": "path", "name": "locale", "required": true, "schema": {"type": "string"}}, {"in": "header", "name": "Idempotency-Key", "required": true, "schema": {"maxLength": 200, "type": "string"}}], "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/TranslationTransition"}}}, "required": true}, "responses": {"200": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/Translation"}}}, "description": "the state changed; revision and text unchanged; exactly one translation_state audit event"}, "400": {"description": "unknown_field, invalid_field, malformed_json, invalid_body or invalid_parameter (missing Idempotency-Key)"}, "403": {"description": "forbidden"}, "404": {"description": "not found in this tenant"}, "409": {"description": "invalid_transition (the stored state is not the action's source state, or untranslated), revision_mismatch, key_obsolete or idempotency_in_progress"}, "422": {"description": "qa_failed on review or approve (detail lists the failed blocking checks, checks carries them; state unchanged), an LS1 tag refusal code or locale_too_long"}, "503": {"$ref": "#/components/responses/LanguageCoreUnavailable"}}, "x-permission": "review and reject: p_<p>_loc_<tag>_review; approve and revoke: p_<p>_loc_<tag>_approve (or the anyloc form)", "x-phase": "P4"}}, "/v1/me": {"get": {"responses": {"200": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/Me"}}}}}, "x-permission": "authenticated", "x-phase": "P1"}}, "/v1/members": {"get": {"parameters": [{"$ref": "#/components/parameters/Cursor"}, {"$ref": "#/components/parameters/Limit"}], "responses": {"200": {"description": "members"}}, "x-permission": "org_members-manage", "x-phase": "P1"}, "post": {"parameters": [{"$ref": "#/components/parameters/IdempotencyKey"}], "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/MemberCreate"}}}}, "responses": {"201": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/Member"}}}}}, "x-permission": "org_members-manage", "x-phase": "P1"}}, "/v1/members/{member_id}": {"delete": {"parameters": [{"in": "path", "name": "member_id", "required": true, "schema": {"format": "uuid", "type": "string"}}], "responses": {"200": {"description": "removed; sessions ended", "keys and grants revoked": null}}, "x-permission": "org_members-manage", "x-phase": "P1"}, "get": {"parameters": [{"in": "path", "name": "member_id", "required": true, "schema": {"format": "uuid", "type": "string"}}], "responses": {"200": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/Member"}}}}}, "x-permission": "org_members-manage", "x-phase": "P1"}}, "/v1/members/{member_id}/erase": {"post": {"parameters": [{"in": "path", "name": "member_id", "required": true, "schema": {"format": "uuid", "type": "string"}}], "responses": {"200": {"description": "personal fields pseudonymised"}}, "x-permission": "org_members-manage", "x-phase": "P1"}}, "/v1/projects": {"get": {"parameters": [{"$ref": "#/components/parameters/Cursor"}, {"$ref": "#/components/parameters/Limit"}], "responses": {"200": {"description": "projects the caller may read"}}, "x-permission": "authenticated", "x-phase": "P1"}, "post": {"parameters": [{"$ref": "#/components/parameters/IdempotencyKey"}], "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/ProjectCreate"}}}}, "responses": {"201": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/Project"}}}}}, "x-permission": "ws_<w>_admin", "x-phase": "P1"}}, "/v1/projects/{project_id}": {"get": {"parameters": [{"in": "path", "name": "project_id", "required": true, "schema": {"format": "uuid", "type": "string"}}], "responses": {"200": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/Project"}}}}, "404": {"description": "not found in this tenant"}}, "x-permission": "p_<p>_read", "x-phase": "P1"}, "patch": {"parameters": [{"in": "path", "name": "project_id", "required": true, "schema": {"format": "uuid", "type": "string"}}], "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/ProjectPatch"}}}}, "responses": {"200": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/Project"}}}}}, "x-permission": "p_<p>_manage", "x-phase": "P1"}}, "/v1/projects/{project_id}/approval-requests": {"post": {"parameters": [{"$ref": "#/components/parameters/ProjectId"}, {"in": "header", "name": "Idempotency-Key", "required": true, "schema": {"maxLength": 200, "type": "string"}}], "requestBody": {"content": {"application/json": {"schema": {"additionalProperties": false, "properties": {"file": {"format": "uuid", "type": "string"}, "locale": {"type": "string"}, "review_url": {"description": "optional https URL of a review page; copied into the Futex context as review_url", "maxLength": 2048, "type": "string"}}, "required": ["file", "locale"], "type": "object"}}}, "required": true}, "responses": {"201": {"description": "an approval request {id, project_id, file_id, locale, file_revision, item_count, status: pending, futex_decision_id, ...} batching (at most 200) the file's draft and reviewed translations in the locale that pass every blocking QA check (Q18 included) and whose key is not under the project's legal_prefixes; one Futex decision under FUTEX_POLICY_ID with idempotency_key = the request id, expires_in_hours 168, proposed_action {project, file, locale, revision, count} and context items {key, source, target, targetHash}; approval_request.created and .submitted audit events"}, "400": {"description": "unknown_field", "invalid_field or a missing Idempotency-Key": null}, "403": {"description": "forbidden"}, "404": {"description": "not found in this tenant"}, "409": {"description": "nothing_to_approve (an empty batch), approval_pending (a request for this file and locale is still open) or idempotency_in_progress"}, "422": {"description": "an LS1 tag refusal code or locale_too_long"}, "503": {"description": "futex_unavailable (Futex unconfigured, unreachable or refusing; nothing is approved; the request is recorded as failed; Retry-After 30)"}}, "x-permission": "p_<p>_release", "x-phase": "P7"}}, "/v1/projects/{project_id}/files": {"post": {"parameters": [{"$ref": "#/components/parameters/ProjectId"}, {"description": "json, yaml and properties are offered; android, ios-strings and ios-stringsdict answer 422 format_not_offered", "in": "query", "name": "kind", "required": true, "schema": {"enum": ["json", "yaml", "properties", "android", "ios-strings", "ios-stringsdict"], "type": "string"}}, {"description": "required for yaml and refused for any other kind; rails excludes the single locale root from every key path", "in": "query", "name": "profile", "required": false, "schema": {"enum": ["rails", "flat"], "type": "string"}}, {"description": "rails root locale; default the project's source locale; refused unless profile=rails", "in": "query", "name": "locale", "required": false, "schema": {"maxLength": 255, "minLength": 1, "type": "string"}}, {"description": "relative resource path; no empty, dot or dot-dot segment, no control character or backslash", "in": "query", "name": "path", "required": true, "schema": {"maxLength": 512, "minLength": 1, "type": "string"}}, {"in": "header", "name": "Idempotency-Key", "required": true, "schema": {"maxLength": 200, "type": "string"}}], "requestBody": {"content": {"application/json": {"schema": {"format": "binary", "maxLength": 52428800, "type": "string"}}, "application/octet-stream": {"schema": {"format": "binary", "maxLength": 52428800, "type": "string"}}}, "required": true}, "responses": {"201": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/FileImported"}}}, "description": "parsed and stored envelope-encrypted as version 1 of a new path, or as version N+1 of the file already at that path (same file_id); keys added, obsoleted (hidden from list and export, translations kept), restored (translations intact) or changed (source replaced, every translation of the key set outdated), unchanged keys untouched; exactly one import audit event with version, sha256, counts added/changed/obsoleted/restored and manifest_sha256, all in one transaction"}, "400": {"description": "unknown_parameter, invalid_parameter (kind, path, profile, locale or a missing Idempotency-Key)"}, "403": {"description": "forbidden"}, "404": {"description": "not found in this tenant"}, "409": {"description": "conflict (a concurrent upload to the same path won the race for that version; nothing changed), format_mismatch (the path holds a file of another kind, profile or root locale; nothing stored) or idempotency_in_progress; a path that already holds a file is not a conflict"}, "413": {"description": "upload_too_large (over 50 MiB)"}, "415": {"description": "unsupported_media_type"}, "422": {"description": "format_not_offered, or the reader of the declared kind refused the file; code is its refusal code (malformed, duplicate_key, yaml_alias_refused, yaml_tag_refused, encoding_invalid, nesting_too_deep, parse_timeout, too_many_units, key_invalid, key_too_long, value_unrepresentable, unsupported_construct, upload_too_large) or idempotency_key_reused"}}, "x-order": "authn, parameters (400), Idempotency-Key absent (400), size (413), media (415), replay, project (404), authz (403), parse (422), concurrent version race (409)", "x-permission": "p_<p>_files-write", "x-phase": "P3"}}, "/v1/projects/{project_id}/files/{file_id}/context": {"get": {"parameters": [{"$ref": "#/components/parameters/ProjectId"}, {"in": "path", "name": "file_id", "required": true, "schema": {"format": "uuid", "type": "string"}}], "responses": {"200": {"description": "{keys: [{path, context, min_length, max_length}]} ordered by path"}, "403": {"description": "forbidden"}, "404": {"description": "not found in this tenant (project or file)"}}, "x-permission": "p_<p>_read", "x-phase": "P6"}, "put": {"parameters": [{"$ref": "#/components/parameters/ProjectId"}, {"in": "path", "name": "file_id", "required": true, "schema": {"format": "uuid", "type": "string"}}, {"in": "header", "name": "Idempotency-Key", "required": true, "schema": {"maxLength": 200, "type": "string"}}], "requestBody": {"content": {"application/json": {"schema": {"additionalProperties": false, "properties": {"keys": {"items": {"additionalProperties": false, "properties": {"class": {"enum": ["prose", "claim", "legal", null], "type": ["string", "null"]}, "context": {"maxLength": 2000, "minLength": 1, "type": ["string", "null"]}, "max_length": {"maximum": 100000, "minimum": 1, "type": ["integer", "null"]}, "min_length": {"maximum": 100000, "minimum": 1, "type": ["integer", "null"]}, "path": {"maxLength": 1024, "minLength": 1, "type": "string"}}, "required": ["path"], "type": "object"}, "maxItems": 1000, "minItems": 1, "type": "array"}}, "required": ["keys"], "type": "object"}}}, "required": true}, "responses": {"200": {"description": "upserts each entry by (file, path); entries survive source re-uploads; AI translate sends context and max_length to the engine; one key_context.set audit event; body {file, keys}"}, "400": {"a duplicate path or a missing Idempotency-Key": null, "description": "unknown_field", "invalid_field": null}, "403": {"description": "forbidden"}, "404": {"description": "not found in this tenant (project or file)"}, "422": {"description": "key_unknown (a path that is not an active key of the file; nothing written), length_range (min_length > max_length) or context_invalid (leading or trailing whitespace)"}}, "x-permission": "p_<p>_files-write", "x-phase": "P6"}}, "/v1/projects/{project_id}/files/{file_id}/export": {"get": {"parameters": [{"$ref": "#/components/parameters/ProjectId"}, {"in": "path", "name": "file_id", "required": true, "schema": {"format": "uuid", "type": "string"}}, {"description": "target locale; absent exports the source", "in": "query", "name": "locale", "required": false, "schema": {"maxLength": 255, "minLength": 1, "type": "string"}}, {"description": "source keeps an untranslated key's source value (only translated spans change); omit removes untranslated object members (array elements keep their source value) and keeps every other byte; needs locale", "in": "query", "name": "fallback", "required": false, "schema": {"default": "source", "enum": ["source", "omit"], "type": "string"}}, {"description": "approved uses only approved translations; every other key falls back per fallback; needs locale; absent keeps every stored translation", "in": "query", "name": "state", "required": false, "schema": {"enum": ["approved"], "type": "string"}}, {"description": "true answers JSON {project_id, file_id, locale, revision, entries} instead of the catalogue: one entry per key of the exported catalogue (flat top-level keys by their own name, nested keys by their dotted path, plural forms as <key>_<category> in JSON) with sourceHash (SHA-256 hex of the as-sent source value the translation was made from; the current source when untranslated), targetHash (SHA-256 hex of the exported value), status (approved, reviewed, draft, needs_attention, outdated, untranslated), approvedAt (RFC 3339) and approvedBy (principal id), both null unless approved; needs locale (SPECS/0011 EX1-EX5)", "in": "query", "name": "metadata", "required": false, "schema": {"default": "false", "enum": ["true", "false"], "type": "string"}}, {"description": "the (file, locale) revision the caller expects; equal to the current revision serves it, a lower one answers 410 revision_superseded with the current ETag, a higher one 404 revision_not_found", "in": "query", "name": "revision", "required": false, "schema": {"minimum": 1, "type": "integer"}}], "responses": {"200": {"content": {"application/json": {"schema": {"format": "binary", "type": "string"}}}, "description": "the file rebuilt from the latest stored version; byte-identical to the latest upload when nothing was edited and no locale is given; with locale, spans of active keys translated in that locale (outdated included) replaced per fallback; exactly one export audit event (locale and fallback recorded)", "headers": {"ETag": {"description": "strong entity tag \"r<revision>\": the (file, locale) revision, max import version plus every change to a translation of the file's keys in that locale (the import version alone without locale); the same for the catalogue and the metadata", "schema": {"pattern": "^\"r[0-9]+\"$", "type": "string"}}}}, "400": {"description": "unknown_parameter or invalid_parameter (fallback, state, metadata or revision value, or fallback, state or metadata without locale)"}, "403": {"description": "forbidden"}, "404": {"description": "not found in this tenant"}, "410": {"description": "revision_superseded: the revision parameter names a superseded revision; carries the current ETag"}, "422": {"description": "an LS1 tag refusal code, locale_too_long (canonical form over 40), or invalid_parameter for fallback=omit on a file that is not JSON"}, "503": {"$ref": "#/components/responses/LanguageCoreUnavailable"}}, "x-permission": "p_<p>_export", "x-phase": "P3"}}, "/v1/projects/{project_id}/files/{file_id}/imports/{seq}/manifest": {"get": {"parameters": [{"$ref": "#/components/parameters/ProjectId"}, {"in": "path", "name": "file_id", "required": true, "schema": {"format": "uuid", "type": "string"}}, {"description": "the import audit event's sequence", "in": "path", "name": "seq", "required": true, "schema": {"minimum": 1, "type": "integer"}}], "responses": {"200": {"content": {"text/tab-separated-values": {"schema": {"type": "string"}}}, "description": "the import manifest: one line per affected key, key id TAB change (added, changed, obsoleted, restored) TAB previous sourceHash ('-' when added) TAB new sourceHash ('-' when obsoleted) LF, sorted by key id; its SHA-256 equals the import event's manifest_sha256"}, "403": {"description": "forbidden"}, "404": {"description": "not found in this tenant (unknown project", "file or import event seq)": null}}, "x-permission": "p_<p>_read", "x-phase": "P3"}}, "/v1/projects/{project_id}/glossary": {"get": {"parameters": [{"$ref": "#/components/parameters/ProjectId"}, {"in": "query", "name": "kind", "required": false, "schema": {"enum": ["term", "dnt", "forbidden"], "type": "string"}}, {"$ref": "#/components/parameters/Cursor"}, {"$ref": "#/components/parameters/Limit"}], "responses": {"200": {"description": "{terms: [entry], next_cursor} ordered by id"}, "400": {"description": "unknown_parameter or invalid_parameter"}, "403": {"description": "forbidden"}, "404": {"description": "not found in this tenant"}}, "x-permission": "p_<p>_read", "x-phase": "P5"}, "post": {"parameters": [{"$ref": "#/components/parameters/ProjectId"}], "requestBody": {"content": {"application/json": {"schema": {"additionalProperties": false, "properties": {"kind": {"enum": ["term", "dnt", "forbidden"], "type": "string"}, "locale": {"maxLength": 255, "minLength": 1, "type": ["string", "null"]}, "note": {"maxLength": 1000, "type": ["string", "null"]}, "source": {"maxLength": 200, "minLength": 1, "type": ["string", "null"]}, "target": {"maxLength": 200, "minLength": 1, "type": ["string", "null"]}}, "required": ["kind"], "type": "object"}}}, "required": true}, "responses": {"201": {"description": "a glossary entry {id, project_id, kind, locale, source, target, note, created_by, created_at, updated_at}; one glossary_term.create audit event. term: source term to target term for a locale (Q09 reports a missing target term; locale de also covers de-DE); dnt: a do-not-translate term that must appear verbatim in every target whose source holds it (Q10, blocking; locale null for every locale); forbidden: a target term that must not appear (Q11, blocking; locale null for every locale)"}, "400": {"description": "unknown_field", "invalid_field or malformed_json": null}, "403": {"description": "forbidden"}, "404": {"description": "not found in this tenant"}, "409": {"description": "term_exists (the same kind", "locale": null, "source and target) or idempotency_in_progress": null}, "422": {"description": "422 term_shape (term needs source, target and locale; dnt needs source and takes no target; forbidden needs target and takes no source; kind cannot change), term_invalid (leading or trailing whitespace or an unprintable character), or an LS1 tag refusal code"}}, "x-permission": "p_<p>_glossary-write", "x-phase": "P5"}}, "/v1/projects/{project_id}/glossary/{term_id}": {"delete": {"parameters": [{"$ref": "#/components/parameters/ProjectId"}, {"in": "path", "name": "term_id", "required": true, "schema": {"format": "uuid", "type": "string"}}], "responses": {"200": {"description": "the deleted entry; one glossary_term.delete audit event"}, "403": {"description": "forbidden"}, "404": {"description": "not found in this tenant"}}, "x-permission": "p_<p>_glossary-write", "x-phase": "P5"}, "get": {"parameters": [{"$ref": "#/components/parameters/ProjectId"}, {"in": "path", "name": "term_id", "required": true, "schema": {"format": "uuid", "type": "string"}}], "responses": {"200": {"description": "a glossary entry {id, project_id, kind, locale, source, target, note, created_by, created_at, updated_at}"}, "403": {"description": "forbidden"}, "404": {"description": "not found in this tenant"}}, "x-permission": "p_<p>_read", "x-phase": "P5"}, "put": {"parameters": [{"$ref": "#/components/parameters/ProjectId"}, {"in": "path", "name": "term_id", "required": true, "schema": {"format": "uuid", "type": "string"}}, {"in": "header", "name": "Idempotency-Key", "required": true, "schema": {"maxLength": 200, "type": "string"}}], "requestBody": {"content": {"application/json": {"schema": {"additionalProperties": false, "properties": {"kind": {"enum": ["term", "dnt", "forbidden"], "type": "string"}, "locale": {"maxLength": 255, "minLength": 1, "type": ["string", "null"]}, "note": {"maxLength": 1000, "type": ["string", "null"]}, "source": {"maxLength": 200, "minLength": 1, "type": ["string", "null"]}, "target": {"maxLength": 200, "minLength": 1, "type": ["string", "null"]}}, "required": ["kind"], "type": "object"}}}, "required": true}, "responses": {"200": {"description": "the replaced entry (same kind); one glossary_term.update audit event"}, "400": {"description": "unknown_field", "invalid_field": null, "malformed_json or a missing Idempotency-Key": null}, "403": {"description": "forbidden"}, "404": {"description": "not found in this tenant"}, "409": {"description": "term_exists or idempotency_in_progress"}, "422": {"description": "422 term_shape (term needs source, target and locale; dnt needs source and takes no target; forbidden needs target and takes no source; kind cannot change), term_invalid (leading or trailing whitespace or an unprintable character), or an LS1 tag refusal code"}}, "x-permission": "p_<p>_glossary-write", "x-phase": "P5"}}, "/v1/projects/{project_id}/keys": {"get": {"parameters": [{"$ref": "#/components/parameters/ProjectId"}, {"$ref": "#/components/parameters/Cursor"}, {"$ref": "#/components/parameters/Limit"}], "responses": {"200": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/KeyPage"}}}, "description": "the project's active keys (obsolete keys excluded) ordered by id"}, "400": {"description": "unknown_parameter or invalid_parameter"}, "403": {"description": "forbidden"}, "404": {"description": "not found in this tenant"}}, "x-permission": "p_<p>_read", "x-phase": "P3"}}, "/v1/projects/{project_id}/licences": {"get": {"parameters": [{"$ref": "#/components/parameters/ProjectId"}], "responses": {"200": {"description": "{licences: [{id, locale, state (requested|active|lapsed|refused|sample_failed), sample_size, corrections, meaning_errors, sample_ref, approval_request_id, futex_decision_id, decided_by, decided_at, lapsed_at, lapse_reason, prose_keys, prose_changed_since_grant, expires_at, created_at}]}; active lapses 30 days after the decision, when more than 10% of the locale's prose keys changed since, or when a later sample fails"}, "403": {"description": "forbidden"}, "404": {"description": "not found in this tenant"}}, "x-permission": "p_<p>_read", "x-phase": "P6"}, "post": {"parameters": [{"$ref": "#/components/parameters/ProjectId"}, {"in": "header", "name": "Idempotency-Key", "required": true, "schema": {"maxLength": 200, "type": "string"}}], "requestBody": {"content": {"application/json": {"schema": {"additionalProperties": false, "properties": {"corrections": {"minimum": 0, "type": "integer"}, "locale": {"maxLength": 255, "minLength": 1, "type": "string"}, "meaning_errors": {"minimum": 0, "type": "integer"}, "sample_ref": {"maxLength": 500, "minLength": 1, "type": "string"}, "sample_size": {"maximum": 100000, "minimum": 300, "type": "integer"}}, "required": ["locale", "sample_size", "corrections", "meaning_errors", "sample_ref"], "type": "object"}}}, "required": true}, "responses": {"201": {"description": "a passing sample (corrections at most 5% and zero meaning errors) raises one Futex decision (state requested; active once approved); a failing sample is recorded as sample_failed with no decision and lapses the active licence"}, "400": {"description": "invalid_field (sample_size under 300)"}, "403": {"description": "forbidden"}, "409": {"description": "licence_pending"}, "422": {"description": "sample_invalid or an LS1 tag refusal code"}, "503": {"description": "futex_unavailable"}}, "x-permission": "p_<p>_release", "x-phase": "P6"}}, "/v1/projects/{project_id}/policy": {"put": {"parameters": [{"$ref": "#/components/parameters/ProjectId"}, {"in": "header", "name": "Idempotency-Key", "required": true, "schema": {"maxLength": 200, "type": "string"}}], "requestBody": {"content": {"application/json": {"schema": {"additionalProperties": false, "properties": {"enabled": {"type": "boolean"}, "locale": {"maxLength": 255, "minLength": 1, "type": "string"}}, "required": ["locale", "enabled"], "type": "object"}}}, "required": true}, "responses": {"200": {"description": "the per-locale kill switch for policy approval (default enabled); one policy_switch.set audit event"}, "400": {"description": "unknown_field or invalid_field"}, "403": {"description": "forbidden"}, "404": {"description": "not found in this tenant"}}, "x-permission": "p_<p>_manage", "x-phase": "P6"}}, "/v1/projects/{project_id}/policy-approve": {"post": {"parameters": [{"$ref": "#/components/parameters/ProjectId"}, {"in": "header", "name": "Idempotency-Key", "required": true, "schema": {"maxLength": 200, "type": "string"}}], "requestBody": {"content": {"application/json": {"schema": {"additionalProperties": false, "properties": {"file": {"format": "uuid", "type": "string"}, "locale": {"maxLength": 255, "minLength": 1, "type": "string"}}, "required": ["file", "locale"], "type": "object"}}}, "required": true}, "responses": {"200": {"description": "approves the file's draft and reviewed translations whose key class is prose, that are not under legal_prefixes and that pass every blocking QA check; approved_by_label policy:<licence>@1, approval_ref = evaluation_id; one translation_state event per key with the licence's Futex decision; body {file, locale, evaluation_id, licence, approved, skipped: {class, legal, qa, state}}"}, "403": {"description": "forbidden"}, "404": {"description": "not found in this tenant (project or file)"}, "409": {"description": "policy_unlicensed (no active licence for the locale) or policy_disabled (kill switch off)"}}, "x-permission": "p_<p>_release", "x-phase": "P6"}}, "/v1/projects/{project_id}/releases": {"post": {"parameters": [{"$ref": "#/components/parameters/ProjectId"}, {"in": "header", "name": "Idempotency-Key", "required": true, "schema": {"maxLength": 200, "type": "string"}}], "responses": {"201": {"content": {"application/json": {"schema": {"description": "{id, project_id, sha256, size, files, locales, entries, created_by, created_at, withdrawn, withdrawn_at}", "type": "object"}}}, "description": "an immutable release: a zip of every file of the project x every locale with at least one approved translation, entry <locale>/<file path> = the file exported with locale, state=approved and fallback=source; zip timestamps fixed and entries sorted; stored envelope-encrypted with its SHA-256; exactly one release audit event; a later approval never changes it", "headers": {"ETag": {"description": "quoted SHA-256 hex of the zip", "schema": {"type": "string"}}}}, "400": {"description": "invalid_parameter (missing Idempotency-Key)"}, "403": {"description": "forbidden"}, "404": {"description": "not found in this tenant"}}, "x-permission": "p_<p>_release", "x-phase": "P7"}}, "/v1/projects/{project_id}/style": {"get": {"parameters": [{"$ref": "#/components/parameters/ProjectId"}], "responses": {"200": {"description": "{styles: [{locale, scope, text, updated_at}]} ordered by locale and scope; locale is a canonical tag or '*' (every locale); scope is default or legal"}, "403": {"description": "forbidden"}, "404": {"description": "not found in this tenant"}}, "x-permission": "p_<p>_read", "x-phase": "P6"}, "put": {"parameters": [{"$ref": "#/components/parameters/ProjectId"}, {"in": "header", "name": "Idempotency-Key", "required": true, "schema": {"maxLength": 200, "type": "string"}}], "requestBody": {"content": {"application/json": {"schema": {"additionalProperties": false, "properties": {"locale": {"maxLength": 255, "minLength": 1, "type": "string"}, "register": {"description": "scope default only, with a text; turns on Q19 for the locale (422 register_scope otherwise)", "enum": ["formal", null], "type": ["string", "null"]}, "scope": {"enum": ["default", "legal"], "type": "string"}, "text": {"maxLength": 8000, "type": "string"}}, "required": ["locale", "scope", "text"], "type": "object"}}}, "required": true}, "responses": {"200": {"description": "stores the style instruction for (locale, scope); an empty text deletes it; AI translate sends the '*' and the locale's default texts, plus the legal texts for keys under legal_prefixes, to the engine as 'style'; one project_style.set audit event; body {styles}"}, "400": {"description": "unknown_field", "invalid_field or a missing Idempotency-Key": null}, "403": {"description": "forbidden"}, "404": {"description": "not found in this tenant"}, "422": {"description": "an LS1 tag refusal code or locale_too_long"}}, "x-permission": "p_<p>_glossary-write", "x-phase": "P6"}}, "/v1/projects/{project_id}/translate": {"post": {"parameters": [{"$ref": "#/components/parameters/ProjectId"}, {"in": "header", "name": "Idempotency-Key", "required": true, "schema": {"maxLength": 200, "type": "string"}}], "requestBody": {"content": {"application/json": {"schema": {"additionalProperties": false, "properties": {"batch": {"default": 50, "maximum": 200, "minimum": 1, "type": "integer"}, "drafted_before": {"format": "date-time", "type": ["string", "null"]}, "file": {"format": "uuid", "type": "string"}, "include_drafts": {"default": false, "type": "boolean"}, "include_outdated": {"default": false, "description": "true also re-drafts outdated keys through the AI path (outdated -> draft, revision + 1); they count in drafted and remaining", "type": "boolean"}, "locale": {"maxLength": 255, "minLength": 1, "type": "string"}, "retry_failed": {"default": false, "description": "retry keys recorded as failed for their current source", "type": "boolean"}}, "required": ["file", "locale"], "type": "object"}}}, "required": true}, "responses": {"200": {"description": "drafts up to batch untranslated keys of the file in the locale, each written in its own transaction as its translation arrives, within a budget of about 25 s (stopped: null, deadline, ai_busy, ai_unavailable or ai_daily_cap; what was drafted persists and a re-run takes only what is left); keys holding an ICU plural, select or selectordinal are expanded into full-sentence variants (target CLDR categories x select keys, =N and offset kept, at most 36), translated as plain sentences (the engine never sees ICU syntax), reassembled into a target-locale ICU message and written only if it passes Q01-Q05 (else failed_keys with reason icu_qa_failed); messages needing more than 36 variants, choice arguments and plural keys holding ICU are reported as needs_human and needs_human_keys, outside remaining: a TM exact match is reused with no engine call, then an engine answer kept for this tenant from an earlier run whose budget ran out before it arrived (identical request: masked text, locales, examples, glossary, forbidden terms, model, prompt sha; via memo, used once, then deleted), otherwise a Prism call (deepseek-v4-flash, temperature 0, json_object) with placeholders masked as sentinels and up to 3 TM fuzzy examples, carrying up to 20 keys that share the locale, style text and forbidden list (each key's answer validated on its own; a key missing, duplicated or invalid in the batch answer is re-asked as a single call); engine_calls counts the calls sent (one per batch, one per single key), tokens their total usage, split evenly across a batch's keys in the per-key reports; a reply whose placeholders do not round-trip is skipped; written as draft through the PUT path with one translation event per key (via, model, x-request-ids, prompt sha); blocking QA failures are listed in qa_failed; body {file, locale, batch, drafted, tm_reused, engine_calls, tokens, remaining, needs_human, needs_human_keys, failed_keys [{path, reason, rejected}], stopped, keys}; a placeholder lost, repeated or invented by the engine is re-asked up to twice, then recorded as failed for the key's current source and not retried unless retry_failed"}, "400": {"description": "unknown_field", "invalid_field or a missing Idempotency-Key": null}, "403": {"description": "forbidden"}, "404": {"description": "not found in this tenant (project or file)"}, "409": {"description": "ai_processing_disabled (the tenant's ai_processing is not approved_routes; the operator enables it with l10n-admin tenant ai --enable, on a test-data-only tenant or with --operator-approved <reference>, D30) or ai_spend_unassigned (no daily AI spend cap is assigned to the tenant; the operator runs l10n-admin tenant ai-cap; nothing sent)"}, "422": {"description": "an LS1 tag refusal code or locale_too_long"}, "429": {"description": "ai_daily_cap (the tenant's daily AI token cap is spent and nothing could be drafted; body limit, used, reset_at; Retry-After seconds to the UTC-day reset); when part of the work fits, the call answers 200 with stopped ai_daily_cap instead"}, "503": {"description": "ai_unavailable (Prism unreachable or unconfigured before anything was drafted; Retry-After 30; nothing written), ai_busy (the Prism bulkhead is full; Retry-After 5; nothing written), metering_unavailable (TokenGate unreachable, so the daily cap cannot be checked; Retry-After 30; nothing sent) or language_core_unavailable"}}, "x-permission": "p_<p>_jobs-run", "x-phase": "P6"}}, "/v1/projects/{project_id}/translations": {"get": {"parameters": [{"$ref": "#/components/parameters/ProjectId"}, {"in": "query", "name": "locale", "required": true, "schema": {"maxLength": 255, "minLength": 1, "type": "string"}}, {"$ref": "#/components/parameters/Cursor"}, {"$ref": "#/components/parameters/Limit"}], "responses": {"200": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/TranslationPage"}}}, "description": "the project's stored translations in the canonical locale ordered by key id"}, "400": {"cursor)": null, "description": "unknown_parameter or invalid_parameter (locale absent", "limit": null}, "403": {"description": "forbidden"}, "404": {"description": "not found in this tenant"}, "422": {"description": "an LS1 tag refusal code or locale_too_long (canonical form over 40)"}, "503": {"$ref": "#/components/responses/LanguageCoreUnavailable"}}, "x-core": true, "x-permission": "p_<p>_read", "x-phase": "P3"}}, "/v1/projects/{project_id}/xliff/export": {"post": {"parameters": [{"$ref": "#/components/parameters/ProjectId"}, {"in": "header", "name": "Idempotency-Key", "required": true, "schema": {"maxLength": 200, "type": "string"}}], "requestBody": {"content": {"application/json": {"schema": {"additionalProperties": false, "properties": {"file": {"format": "uuid", "type": "string"}, "locale": {"maxLength": 255, "minLength": 1, "type": "string"}}, "required": ["file", "locale"], "type": "object"}}}, "required": true}, "responses": {"200": {"content": {"application/xliff+xml": {"schema": {"format": "binary", "type": "string"}}}, "description": "an XLIFF 2.0 document (D26): one unit per active key of the file, one unit per category of a plural key (id key_id.category); state map untranslated->initial, draft->translated, reviewed->reviewed, approved->final, outdated->initial with subState l10n:outdated; characters XML cannot carry as <cp/>; exactly one export audit event (format xliff-2.0)"}, "400": {"description": "unknown_field", "invalid_field or a missing Idempotency-Key": null}, "403": {"description": "forbidden"}, "404": {"description": "not found in this tenant (project or file)"}, "422": {"description": "an LS1 tag refusal code or locale_too_long"}, "503": {"$ref": "#/components/responses/LanguageCoreUnavailable"}}, "x-permission": "p_<p>_export", "x-phase": "P7"}}, "/v1/projects/{project_id}/xliff/import": {"post": {"parameters": [{"$ref": "#/components/parameters/ProjectId"}, {"in": "header", "name": "Idempotency-Key", "required": true, "schema": {"maxLength": 200, "type": "string"}}], "requestBody": {"content": {"application/octet-stream": {"schema": {"format": "binary", "maxLength": 52428800, "type": "string"}}}, "required": true}, "responses": {"200": {"description": "targets of the units applied in one transaction (XL2, XL3): a changed target is written (revision +1) at the requested state; final needs approve and reviewed needs review in trgLang, else the unit is stored draft (clamped permission); a state above draft whose blocking QA fails is stored draft (clamped qa); an unchanged target only moves draft->reviewed->approved, never down; a unit without target is skipped; one translation event per written key and one xliff_import event; body {locale, written, units: [{unit, key_id, result written|unchanged|skipped, requested, state, clamped, checks}]}"}, "400": {"description": "invalid_parameter (missing Idempotency-Key)"}, "403": {"description": "forbidden"}, "404": {"description": "not found in this tenant"}, "413": {"description": "upload_too_large"}, "415": {"description": "unsupported_media_type"}, "422": {"description": "source_changed (a unit source or srcLang differs from the stored source; nothing written), xliff_invalid, xliff_unit_unknown, xliff_shape, xliff_inline_unsupported, value_too_long, value_unrepresentable, an LS1 tag refusal code"}}, "x-permission": "p_<p>_loc_<trgLang>_translate", "x-phase": "P7"}}, "/v1/releases/{release_id}": {"get": {"parameters": [{"in": "path", "name": "release_id", "required": true, "schema": {"format": "uuid", "type": "string"}}], "responses": {"200": {"content": {"application/json": {"schema": {"description": "{id, project_id, sha256, size, files, locales, entries, created_by, created_at, withdrawn, withdrawn_at}", "type": "object"}}}, "description": "the release record (withdrawn releases included)"}, "403": {"description": "forbidden"}, "404": {"description": "not found in this tenant"}}, "x-permission": "p_<p>_read", "x-phase": "P7"}}, "/v1/releases/{release_id}/bundle.zip": {"get": {"parameters": [{"in": "path", "name": "release_id", "required": true, "schema": {"format": "uuid", "type": "string"}}, {"in": "header", "name": "If-None-Match", "required": false, "schema": {"type": "string"}}], "responses": {"200": {"byte-identical on every read": null, "content": {"application/zip": {"schema": {"format": "binary", "type": "string"}}}, "description": "the release zip", "headers": {"ETag": {"description": "strong", "quoted SHA-256 hex of the body": null, "schema": {"type": "string"}}}}, "304": {"description": "If-None-Match carries the ETag"}, "403": {"description": "forbidden"}, "404": {"description": "not found in this tenant"}, "410": {"description": "release_withdrawn"}}, "x-permission": "p_<p>_export", "x-phase": "P7"}}, "/v1/releases/{release_id}/withdraw": {"post": {"parameters": [{"in": "path", "name": "release_id", "required": true, "schema": {"format": "uuid", "type": "string"}}, {"in": "header", "name": "Idempotency-Key", "required": true, "schema": {"maxLength": 200, "type": "string"}}], "responses": {"200": {"content": {"application/json": {"schema": {"description": "{id, project_id, sha256, size, files, locales, entries, created_by, created_at, withdrawn, withdrawn_at}", "type": "object"}}}, "description": "withdrawn; the bundle answers 410 from now on; one release_withdraw audit event"}, "403": {"description": "forbidden"}, "404": {"description": "not found in this tenant"}, "410": {"description": "release_withdrawn (already withdrawn)"}}, "x-permission": "p_<p>_release", "x-phase": "P7"}}, "/v1/service-accounts": {"get": {"responses": {"200": {"description": "service accounts"}}, "x-permission": "org_members-manage", "x-phase": "P1"}, "post": {"parameters": [{"$ref": "#/components/parameters/IdempotencyKey"}], "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/ServiceAccountCreate"}}}}, "responses": {"201": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/ServiceAccount"}}}}}, "x-permission": "org_members-manage", "x-phase": "P1"}}, "/v1/service-accounts/{service_account_id}": {"delete": {"parameters": [{"in": "path", "name": "service_account_id", "required": true, "schema": {"format": "uuid", "type": "string"}}], "responses": {"200": {"description": "revoked with all keys"}}, "x-permission": "org_members-manage", "x-phase": "P1"}, "get": {"parameters": [{"in": "path", "name": "service_account_id", "required": true, "schema": {"format": "uuid", "type": "string"}}], "responses": {"200": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/ServiceAccount"}}}}}, "x-permission": "org_members-manage", "x-phase": "P1"}}, "/v1/service-accounts/{service_account_id}/keys": {"post": {"parameters": [{"in": "path", "name": "service_account_id", "required": true, "schema": {"format": "uuid", "type": "string"}}, {"$ref": "#/components/parameters/IdempotencyKey"}], "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/ApiKeyCreate"}}}}, "responses": {"201": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/ApiKeyIssued"}}}}, "409": {"description": "key_limit (20 active)"}}, "x-permission": "org_members-manage", "x-phase": "P1"}}, "/v1/service-accounts/{service_account_id}/keys/{key_id}": {"delete": {"parameters": [{"in": "path", "name": "service_account_id", "required": true, "schema": {"format": "uuid", "type": "string"}}, {"in": "path", "name": "key_id", "required": true, "schema": {"type": "string"}}], "responses": {"200": {"description": "revoked; purged from caches"}}, "x-permission": "org_members-manage", "x-phase": "P1"}}, "/v1/settings": {"get": {"responses": {"200": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/Settings"}}}}}, "x-permission": "org_settings-manage", "x-phase": "P1"}, "put": {"requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/Settings"}}}}, "responses": {"200": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/Settings"}}}}}, "x-permission": "org_settings-manage", "x-phase": "P1"}}, "/v1/usage": {"get": {"responses": {"200": {"content": {"application/json": {"schema": {"type": "object"}}}, "description": "the tenant's live AI spend today from TokenGate: {ai_tokens: {limit, used, remaining, window_start, reset_at, capped}}; tokens are prompt + completion + reasoning tokens of every engine call (repairs, batch fallbacks and late answers included); the window is the UTC day; capped false (limit, used, remaining null) means no daily cap is assigned and AI translate is refused 409 ai_spend_unassigned"}, "403": {"description": "forbidden"}, "503": {"description": "metering_unavailable (TokenGate unreachable; Retry-After 30)"}}, "x-permission": "org_usage-read", "x-phase": "P6"}}, "/v1/workspaces": {"get": {"parameters": [{"$ref": "#/components/parameters/Cursor"}, {"$ref": "#/components/parameters/Limit"}], "responses": {"200": {"description": "workspaces of the caller's tenant"}}, "x-permission": "authenticated", "x-phase": "P1"}, "post": {"parameters": [{"$ref": "#/components/parameters/IdempotencyKey"}], "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/WorkspaceCreate"}}}}, "responses": {"201": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/Workspace"}}}}}, "x-permission": "org_admin", "x-phase": "P1"}}, "/v1/workspaces/{workspace_id}": {"get": {"parameters": [{"in": "path", "name": "workspace_id", "required": true, "schema": {"format": "uuid", "type": "string"}}], "responses": {"200": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/Workspace"}}}}, "404": {"description": "not found in this tenant"}}, "x-permission": "ws_<w>_admin | any p_* in w", "x-phase": "P1"}}}, "servers": [{"url": "https://l10n.rodmena.co.uk"}], "x-parked": {"paths": {"/v1/locales": {"get": {"responses": {"200": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/LocaleList"}}}, "description": "the reviewed profiles ordered by tag"}, "400": {"description": "unknown_parameter"}, "503": {"$ref": "#/components/responses/LanguageCoreUnavailable"}}, "x-core": true, "x-permission": "authenticated", "x-phase": "P2"}}, "/v1/locales/validate": {"post": {"requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/LocaleValidate"}}}}, "responses": {"200": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/LocaleValidation"}}}, "description": "valid, or refused with a stable LS1 code; the input is never echoed"}, "400": {"description": "body shape (tag missing", "longer than 64": null, "not a string": null, "unknown field)": null}, "503": {"$ref": "#/components/responses/LanguageCoreUnavailable"}}, "x-core": true, "x-idempotency": "exempt", "x-permission": "authenticated", "x-phase": "P2"}}, "/v1/projects/{project_id}/locales": {"get": {"parameters": [{"$ref": "#/components/parameters/ProjectId"}], "responses": {"200": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/ProjectLocales"}}}, "description": "the target set; not x-core (keeps answering while the pins fail)", "headers": {"ETag": {"$ref": "#/components/headers/ETag"}}}, "403": {"description": "forbidden"}, "404": {"description": "not found in this tenant"}}, "x-permission": "p_<p>_read", "x-phase": "P2"}, "put": {"parameters": [{"$ref": "#/components/parameters/ProjectId"}, {"$ref": "#/components/parameters/IfMatch"}], "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/ProjectLocalesPut"}}}}, "responses": {"200": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/ProjectLocales"}}}, "description": "the target set now equals the submitted set by identity key; identity-equal targets keep their stored spelling; version +1 only when the identity set changed", "headers": {"ETag": {"$ref": "#/components/headers/ETag"}}}, "400": {"description": "body shape"}, "403": {"description": "forbidden"}, "404": {"description": "not found in this tenant"}, "409": {"description": "locale_has_grants (a dropped target is named by a live grant) or the stale-row refusal"}, "412": {"$ref": "#/components/responses/PreconditionFailed"}, "422": {"description": "an LS1 refusal code, locale_duplicate or locale_is_source; field locales"}, "428": {"$ref": "#/components/responses/PreconditionRequired"}, "503": {"$ref": "#/components/responses/LanguageCoreUnavailable"}}, "x-core": true, "x-order": "authn, shape (400), project (404), authz (403), If-Match (428/412), stale rows (409), semantics (422/409)", "x-permission": "p_<p>_manage", "x-phase": "P2"}}, "/v1/projects/{project_id}/source-locale": {"get": {"parameters": [{"$ref": "#/components/parameters/ProjectId"}], "responses": {"200": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/SourceLocale"}}}, "description": "the source locale; not x-core (keeps answering while the pins fail)", "headers": {"ETag": {"$ref": "#/components/headers/SourceETag"}}}, "403": {"description": "forbidden"}, "404": {"description": "not found in this tenant"}}, "x-permission": "p_<p>_read", "x-phase": "P2"}, "put": {"parameters": [{"$ref": "#/components/parameters/ProjectId"}, {"$ref": "#/components/parameters/IfMatchSource"}], "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/SourceLocalePut"}}}}, "responses": {"200": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/SourceLocale"}}}, "description": "source stored as its content form; identity-equal keeps the stored spelling and version; version +1 only when the identity key changed; the locales ETag is unchanged", "headers": {"ETag": {"$ref": "#/components/headers/SourceETag"}}}, "400": {"description": "body shape"}, "403": {"description": "forbidden"}, "404": {"description": "not found in this tenant"}, "409": {"description": "locale_source_is_target (the identity key equals a current target)"}, "412": {"$ref": "#/components/responses/SourcePreconditionFailed"}, "422": {"description": "an LS1 refusal code (locale_too_long for a content form over 64) or locale_source_not_countable"}, "428": {"$ref": "#/components/responses/PreconditionRequired"}, "503": {"$ref": "#/components/responses/LanguageCoreUnavailable"}}, "x-core": true, "x-order": "authn, shape (400), project (404), authz (403), If-Match (428/412), stale rows (409; exempt when the value converts and is countable), semantics (422/409)", "x-permission": "p_<p>_manage", "x-phase": "P2"}}}, "reason": "P2-D #49 is parked: these locale operations are not served on this line; they return to paths when P2-D lands"}}