From 4486e13d74926b1f97e50c9893b48fc85c38c4cc Mon Sep 17 00:00:00 2001 From: Stephen Lumenta Date: Mon, 15 Jun 2026 16:58:39 +0200 Subject: [PATCH 1/5] feat(API): improve get /projects/{project_id}/keys/{id}/key_links documentation --- doc/compiled.json | 76 ++++++++++++++++++++++++++++++++++---- paths/key_links/index.yaml | 63 ++++++++++++++++++++++++++++--- 2 files changed, 126 insertions(+), 13 deletions(-) diff --git a/doc/compiled.json b/doc/compiled.json index afba5090..a93a677f 100644 --- a/doc/compiled.json +++ b/doc/compiled.json @@ -30879,7 +30879,7 @@ }, "get": { "summary": "List child keys of a parent key", - "description": "Returns detailed information about a parent key, including its linked child keys.", + "description": "Returns the key link record for a parent key, including all child keys associated with it. Key linking lets translation keys share translations — a child key inherits content from its designated parent. Use this endpoint to inspect which keys are linked under a given parent before unlinking them or auditing translation consistency across related keys.\n\nThe key identified by {id} must be designated as a parent key (it must have at least one child key linked to it). Keys that have not been promoted to parent status return a 400 response with the error code key_not_a_parent. Error codes use lowercase snake_case format.\n\nRequires the translations:write scope and manage permission on translation key link references.\n", "operationId": "key_links/index", "tags": [ "Linked Keys" @@ -30895,6 +30895,12 @@ "$ref": "#/components/parameters/key_id_as_id" } ], + "x-code-samples": [ + { + "lang": "Curl", + "source": "curl \"https://api.phrase.com/v2/projects/:project_id/keys/:id/key_links\" \\\n -u USERNAME_OR_ACCESS_TOKEN" + } + ], "responses": { "200": { "description": "OK", @@ -30902,39 +30908,93 @@ "application/json": { "schema": { "$ref": "#/components/schemas/key_link" + }, + "example": { + "created_at": "2024-03-12T09:15:00.000Z", + "updated_at": "2024-05-20T14:32:00.000Z", + "created_by": { + "id": "a1b2c3d4a1b2c3d4a1b2c3d4a1b2c3d4", + "username": "jane.doe", + "name": "Jane Doe", + "gravatar_uid": "abcdef1234567890" + }, + "updated_by": { + "id": "a1b2c3d4a1b2c3d4a1b2c3d4a1b2c3d4", + "username": "jane.doe", + "name": "Jane Doe", + "gravatar_uid": "abcdef1234567890" + }, + "account": { + "id": "abcd1234abcd1234abcd1234abcd1234", + "name": "Acme Corp", + "slug": "acme-corp", + "company": "Acme Corporation", + "created_at": "2023-01-10T08:00:00.000Z", + "updated_at": "2024-01-10T08:00:00.000Z" + }, + "parent": { + "id": "f1e2d3c4f1e2d3c4f1e2d3c4f1e2d3c4", + "name": "button.submit", + "plural": false, + "use_ordinal_rules": false + }, + "children": [ + { + "id": "9a8b7c6d9a8b7c6d9a8b7c6d9a8b7c6d", + "name": "button.submit.mobile", + "plural": false, + "use_ordinal_rules": false + }, + { + "id": "1a2b3c4d1a2b3c4d1a2b3c4d1a2b3c4d", + "name": "button.submit.web", + "plural": false, + "use_ordinal_rules": false + } + ] } } } }, "400": { - "description": "Bad Request", + "description": "Bad request. Returned when the key identified by {id} is not designated as a parent key. To resolve, promote the key to parent status by linking child keys to it first, or supply the code of an existing parent key.", "content": { "application/json": { "schema": { "type": "object", + "title": "key_links/index/bad_request", "properties": { "message": { - "type": "string" + "type": "string", + "description": "Human-readable description of the error." + }, + "error": { + "type": "string", + "description": "Stable machine-readable error code in lowercase snake_case format (e.g. key_not_a_parent). Branch on this field rather than the message string." } - }, - "example": { - "message": "Key is not a parent key" } + }, + "example": { + "message": "Key button.submit is not a parent key", + "error": "key_not_a_parent" } } } }, "401": { + "description": "Unauthorized. The access token is missing or invalid. Supply a valid access token in the Authorization header.", "$ref": "#/components/responses/401" }, "403": { - "$ref": "#/components/responses/403", - "description": "Forbidden. Returned when the access token lacks the `write` scope or when the requesting user is not allowed to read key links for this key." + "description": "Forbidden. The access token lacks the translations:write scope, or the account member does not have manage permission on translation key link references.", + "$ref": "#/components/responses/403" }, "404": { + "description": "Not found. No key with the given code exists in this project. Verify the project_id and the key code before retrying.", "$ref": "#/components/responses/404" }, "429": { + "description": "Too many requests. Wait for the interval indicated by the X-Rate-Limit-Reset header before retrying.", "$ref": "#/components/responses/429" } } diff --git a/paths/key_links/index.yaml b/paths/key_links/index.yaml index c74519ba..a6bb961e 100644 --- a/paths/key_links/index.yaml +++ b/paths/key_links/index.yaml @@ -1,6 +1,11 @@ --- summary: List child keys of a parent key -description: Returns detailed information about a parent key, including its linked child keys. +description: | + Returns the key link record for a parent key, including all child keys associated with it. Key linking lets translation keys share translations — a child key inherits content from its designated parent. Use this endpoint to inspect which keys are linked under a given parent before unlinking them or auditing translation consistency across related keys. + + The key identified by {id} must be designated as a parent key (it must have at least one child key linked to it). Keys that have not been promoted to parent status return a 400 response with the error code key_not_a_parent. Error codes use lowercase snake_case format. + + Requires the translations:write scope and manage permission on translation key link references. operationId: key_links/index tags: - Linked Keys @@ -8,6 +13,11 @@ parameters: - "$ref": "../../parameters.yaml#/X-PhraseApp-OTP" - "$ref": "../../parameters.yaml#/project_id" - "$ref": "../../parameters.yaml#/key_id_as_id" +x-code-samples: +- lang: Curl + source: |- + curl "https://api.phrase.com/v2/projects/:project_id/keys/:id/key_links" \ + -u USERNAME_OR_ACCESS_TOKEN responses: '200': description: OK @@ -15,23 +25,66 @@ responses: application/json: schema: $ref: "../../schemas/key_link.yaml#/key_link" + example: + created_at: "2024-03-12T09:15:00.000Z" + updated_at: "2024-05-20T14:32:00.000Z" + created_by: + id: "a1b2c3d4a1b2c3d4a1b2c3d4a1b2c3d4" + username: "jane.doe" + name: "Jane Doe" + gravatar_uid: "abcdef1234567890" + updated_by: + id: "a1b2c3d4a1b2c3d4a1b2c3d4a1b2c3d4" + username: "jane.doe" + name: "Jane Doe" + gravatar_uid: "abcdef1234567890" + account: + id: "abcd1234abcd1234abcd1234abcd1234" + name: "Acme Corp" + slug: "acme-corp" + company: "Acme Corporation" + created_at: "2023-01-10T08:00:00.000Z" + updated_at: "2024-01-10T08:00:00.000Z" + parent: + id: "f1e2d3c4f1e2d3c4f1e2d3c4f1e2d3c4" + name: "button.submit" + plural: false + use_ordinal_rules: false + children: + - id: "9a8b7c6d9a8b7c6d9a8b7c6d9a8b7c6d" + name: "button.submit.mobile" + plural: false + use_ordinal_rules: false + - id: "1a2b3c4d1a2b3c4d1a2b3c4d1a2b3c4d" + name: "button.submit.web" + plural: false + use_ordinal_rules: false '400': - description: Bad Request + description: Bad request. Returned when the key identified by {id} is not designated as a parent key. To resolve, promote the key to parent status by linking child keys to it first, or supply the code of an existing parent key. content: application/json: schema: type: object + title: key_links/index/bad_request properties: message: type: string - example: - message: "Key is not a parent key" + description: Human-readable description of the error. + error: + type: string + description: Stable machine-readable error code in lowercase snake_case format (e.g. key_not_a_parent). Branch on this field rather than the message string. + example: + message: "Key button.submit is not a parent key" + error: "key_not_a_parent" '401': + description: Unauthorized. The access token is missing or invalid. Supply a valid access token in the Authorization header. "$ref": "../../responses.yaml#/401" '403': + description: Forbidden. The access token lacks the translations:write scope, or the account member does not have manage permission on translation key link references. "$ref": "../../responses.yaml#/403" - description: Forbidden. Returned when the access token lacks the `write` scope or when the requesting user is not allowed to read key links for this key. '404': + description: Not found. No key with the given code exists in this project. Verify the project_id and the key code before retrying. "$ref": "../../responses.yaml#/404" '429': + description: Too many requests. Wait for the interval indicated by the X-Rate-Limit-Reset header before retrying. "$ref": "../../responses.yaml#/429" From b6fc12ac22c27523cbee86474f92ddf0f070ee5b Mon Sep 17 00:00:00 2001 From: Stephen Lumenta Date: Wed, 17 Jun 2026 21:18:06 +0100 Subject: [PATCH 2/5] fix(API): clean up get /projects/{project_id}/keys/{id}/key_links Apply the batch review conventions: - Drop the scope/permission sentence ('Requires the translations:write scope ...') from the description; the 403 response already documents it. - Remove the 'return a 400 response with error code key_not_a_parent' status sentence; the 400 response carries that detail. Keep the parent-key precondition and the error-code-format note. Co-Authored-By: Claude Opus 4.8 (1M context) --- doc/compiled.json | 2 +- paths/key_links/index.yaml | 4 +--- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/doc/compiled.json b/doc/compiled.json index a93a677f..d99aebe0 100644 --- a/doc/compiled.json +++ b/doc/compiled.json @@ -30879,7 +30879,7 @@ }, "get": { "summary": "List child keys of a parent key", - "description": "Returns the key link record for a parent key, including all child keys associated with it. Key linking lets translation keys share translations — a child key inherits content from its designated parent. Use this endpoint to inspect which keys are linked under a given parent before unlinking them or auditing translation consistency across related keys.\n\nThe key identified by {id} must be designated as a parent key (it must have at least one child key linked to it). Keys that have not been promoted to parent status return a 400 response with the error code key_not_a_parent. Error codes use lowercase snake_case format.\n\nRequires the translations:write scope and manage permission on translation key link references.\n", + "description": "Returns the key link record for a parent key, including all child keys associated with it. Key linking lets translation keys share translations — a child key inherits content from its designated parent. Use this endpoint to inspect which keys are linked under a given parent before unlinking them or auditing translation consistency across related keys.\n\nThe key identified by {id} must be designated as a parent key (it must have at least one child key linked to it). Error codes use lowercase snake_case format.\n", "operationId": "key_links/index", "tags": [ "Linked Keys" diff --git a/paths/key_links/index.yaml b/paths/key_links/index.yaml index a6bb961e..8835e6b5 100644 --- a/paths/key_links/index.yaml +++ b/paths/key_links/index.yaml @@ -3,9 +3,7 @@ summary: List child keys of a parent key description: | Returns the key link record for a parent key, including all child keys associated with it. Key linking lets translation keys share translations — a child key inherits content from its designated parent. Use this endpoint to inspect which keys are linked under a given parent before unlinking them or auditing translation consistency across related keys. - The key identified by {id} must be designated as a parent key (it must have at least one child key linked to it). Keys that have not been promoted to parent status return a 400 response with the error code key_not_a_parent. Error codes use lowercase snake_case format. - - Requires the translations:write scope and manage permission on translation key link references. + The key identified by {id} must be designated as a parent key (it must have at least one child key linked to it). Error codes use lowercase snake_case format. operationId: key_links/index tags: - Linked Keys From 3cdc8679907ce827aa2ba5295c591cb096b9dda2 Mon Sep 17 00:00:00 2001 From: Stephen Lumenta Date: Wed, 24 Jun 2026 09:09:43 +0200 Subject: [PATCH 3/5] docs(API): move error-response prose to shared responses.yaml Review feedback: error response descriptions should stay in the shared responses.yaml for consistency, not be re-authored inline per operation. Under OpenAPI 3.0 a description sibling of $ref is ignored anyway, so the inline text never rendered. Strip the inline $ref-sibling response descriptions (now bare $refs); the shared response owns the wording. Genuinely custom (non-$ref) response bodies are left intact. Co-Authored-By: Claude Opus 4.8 (1M context) --- doc/compiled.json | 4 ---- paths/key_links/index.yaml | 4 ---- 2 files changed, 8 deletions(-) diff --git a/doc/compiled.json b/doc/compiled.json index d99aebe0..fe026f7e 100644 --- a/doc/compiled.json +++ b/doc/compiled.json @@ -30982,19 +30982,15 @@ } }, "401": { - "description": "Unauthorized. The access token is missing or invalid. Supply a valid access token in the Authorization header.", "$ref": "#/components/responses/401" }, "403": { - "description": "Forbidden. The access token lacks the translations:write scope, or the account member does not have manage permission on translation key link references.", "$ref": "#/components/responses/403" }, "404": { - "description": "Not found. No key with the given code exists in this project. Verify the project_id and the key code before retrying.", "$ref": "#/components/responses/404" }, "429": { - "description": "Too many requests. Wait for the interval indicated by the X-Rate-Limit-Reset header before retrying.", "$ref": "#/components/responses/429" } } diff --git a/paths/key_links/index.yaml b/paths/key_links/index.yaml index 8835e6b5..57b79593 100644 --- a/paths/key_links/index.yaml +++ b/paths/key_links/index.yaml @@ -75,14 +75,10 @@ responses: message: "Key button.submit is not a parent key" error: "key_not_a_parent" '401': - description: Unauthorized. The access token is missing or invalid. Supply a valid access token in the Authorization header. "$ref": "../../responses.yaml#/401" '403': - description: Forbidden. The access token lacks the translations:write scope, or the account member does not have manage permission on translation key link references. "$ref": "../../responses.yaml#/403" '404': - description: Not found. No key with the given code exists in this project. Verify the project_id and the key code before retrying. "$ref": "../../responses.yaml#/404" '429': - description: Too many requests. Wait for the interval indicated by the X-Rate-Limit-Reset header before retrying. "$ref": "../../responses.yaml#/429" From 4a890c04b74f3fd7d6d3dab07fd2a4148bb3e036 Mon Sep 17 00:00:00 2001 From: Stephen Lumenta Date: Wed, 24 Jun 2026 09:49:28 +0200 Subject: [PATCH 4/5] docs(API): drop fabricated 400 error-code body, use shared 400 response Source walk (strings-app linked_keys_controller#index) confirms the 400 for a non-parent key renders a bare message ("Key is not a parent key"), not an object with a stable machine-readable code. The custom 400 here invented a top-level `error` code field (key_not_a_parent) the API does not emit, and the description claimed "Error codes use lowercase snake_case". Replace the custom 400 with the shared $ref and keep the genuine cause (the key is not a parent) in the operation description, without inventing codes. Co-Authored-By: Claude Opus 4.8 (1M context) --- doc/compiled.json | 26 ++------------------------ paths/key_links/index.yaml | 19 ++----------------- 2 files changed, 4 insertions(+), 41 deletions(-) diff --git a/doc/compiled.json b/doc/compiled.json index fe026f7e..d35eb8c4 100644 --- a/doc/compiled.json +++ b/doc/compiled.json @@ -30879,7 +30879,7 @@ }, "get": { "summary": "List child keys of a parent key", - "description": "Returns the key link record for a parent key, including all child keys associated with it. Key linking lets translation keys share translations — a child key inherits content from its designated parent. Use this endpoint to inspect which keys are linked under a given parent before unlinking them or auditing translation consistency across related keys.\n\nThe key identified by {id} must be designated as a parent key (it must have at least one child key linked to it). Error codes use lowercase snake_case format.\n", + "description": "Returns the key link record for a parent key, including all child keys associated with it. Key linking lets translation keys share translations — a child key inherits content from its designated parent. Use this endpoint to inspect which keys are linked under a given parent before unlinking them or auditing translation consistency across related keys.\n\nThe key identified by `id` must be designated as a parent key (it must have at least one child key linked to it). Listing the links of a key that is not a parent returns 400.\n", "operationId": "key_links/index", "tags": [ "Linked Keys" @@ -30957,29 +30957,7 @@ } }, "400": { - "description": "Bad request. Returned when the key identified by {id} is not designated as a parent key. To resolve, promote the key to parent status by linking child keys to it first, or supply the code of an existing parent key.", - "content": { - "application/json": { - "schema": { - "type": "object", - "title": "key_links/index/bad_request", - "properties": { - "message": { - "type": "string", - "description": "Human-readable description of the error." - }, - "error": { - "type": "string", - "description": "Stable machine-readable error code in lowercase snake_case format (e.g. key_not_a_parent). Branch on this field rather than the message string." - } - } - }, - "example": { - "message": "Key button.submit is not a parent key", - "error": "key_not_a_parent" - } - } - } + "$ref": "#/components/responses/400" }, "401": { "$ref": "#/components/responses/401" diff --git a/paths/key_links/index.yaml b/paths/key_links/index.yaml index 57b79593..e1e55099 100644 --- a/paths/key_links/index.yaml +++ b/paths/key_links/index.yaml @@ -3,7 +3,7 @@ summary: List child keys of a parent key description: | Returns the key link record for a parent key, including all child keys associated with it. Key linking lets translation keys share translations — a child key inherits content from its designated parent. Use this endpoint to inspect which keys are linked under a given parent before unlinking them or auditing translation consistency across related keys. - The key identified by {id} must be designated as a parent key (it must have at least one child key linked to it). Error codes use lowercase snake_case format. + The key identified by `id` must be designated as a parent key (it must have at least one child key linked to it). Listing the links of a key that is not a parent returns 400. operationId: key_links/index tags: - Linked Keys @@ -58,22 +58,7 @@ responses: plural: false use_ordinal_rules: false '400': - description: Bad request. Returned when the key identified by {id} is not designated as a parent key. To resolve, promote the key to parent status by linking child keys to it first, or supply the code of an existing parent key. - content: - application/json: - schema: - type: object - title: key_links/index/bad_request - properties: - message: - type: string - description: Human-readable description of the error. - error: - type: string - description: Stable machine-readable error code in lowercase snake_case format (e.g. key_not_a_parent). Branch on this field rather than the message string. - example: - message: "Key button.submit is not a parent key" - error: "key_not_a_parent" + "$ref": "../../responses.yaml#/400" '401': "$ref": "../../responses.yaml#/401" '403': From f4686e7eec15655490407544a91b3b314dbafd01 Mon Sep 17 00:00:00 2001 From: Stephen Lumenta Date: Wed, 24 Jun 2026 10:29:04 +0200 Subject: [PATCH 5/5] docs(API): remove hand-written x-code-samples Strings convention: never hand-author Curl or CLI v2 samples. Mintlify renders the request sample (curl + the multi-language playground) and the response from the OpenAPI operation itself, so a hand-written x-code-samples block drifts and overrides the correct auto-generated one; a CLI v2 sample is not spec-derivable at all. Remove the block so Mintlify renders. Co-Authored-By: Claude Opus 4.8 (1M context) --- doc/compiled.json | 6 ------ paths/key_links/index.yaml | 5 ----- 2 files changed, 11 deletions(-) diff --git a/doc/compiled.json b/doc/compiled.json index d35eb8c4..cdb76302 100644 --- a/doc/compiled.json +++ b/doc/compiled.json @@ -30895,12 +30895,6 @@ "$ref": "#/components/parameters/key_id_as_id" } ], - "x-code-samples": [ - { - "lang": "Curl", - "source": "curl \"https://api.phrase.com/v2/projects/:project_id/keys/:id/key_links\" \\\n -u USERNAME_OR_ACCESS_TOKEN" - } - ], "responses": { "200": { "description": "OK", diff --git a/paths/key_links/index.yaml b/paths/key_links/index.yaml index e1e55099..498dc028 100644 --- a/paths/key_links/index.yaml +++ b/paths/key_links/index.yaml @@ -11,11 +11,6 @@ parameters: - "$ref": "../../parameters.yaml#/X-PhraseApp-OTP" - "$ref": "../../parameters.yaml#/project_id" - "$ref": "../../parameters.yaml#/key_id_as_id" -x-code-samples: -- lang: Curl - source: |- - curl "https://api.phrase.com/v2/projects/:project_id/keys/:id/key_links" \ - -u USERNAME_OR_ACCESS_TOKEN responses: '200': description: OK