diff --git a/_assets/images/api-key-permissions-badge-hover.png b/_assets/images/api-key-permissions-badge-hover.png new file mode 100644 index 0000000..f71acdb Binary files /dev/null and b/_assets/images/api-key-permissions-badge-hover.png differ diff --git a/_assets/images/api-key-permissions-badges.png b/_assets/images/api-key-permissions-badges.png new file mode 100644 index 0000000..2c6bd2c Binary files /dev/null and b/_assets/images/api-key-permissions-badges.png differ diff --git a/_assets/images/api-key-permissions-create.png b/_assets/images/api-key-permissions-create.png new file mode 100644 index 0000000..0d30ddc Binary files /dev/null and b/_assets/images/api-key-permissions-create.png differ diff --git a/_assets/images/api-key-permissions-edit-dialog.png b/_assets/images/api-key-permissions-edit-dialog.png new file mode 100644 index 0000000..d209d7a Binary files /dev/null and b/_assets/images/api-key-permissions-edit-dialog.png differ diff --git a/_assets/images/api-key-permissions-edit-menu.png b/_assets/images/api-key-permissions-edit-menu.png new file mode 100644 index 0000000..8063465 Binary files /dev/null and b/_assets/images/api-key-permissions-edit-menu.png differ diff --git a/api-reference/openapi.json b/api-reference/openapi.json index 35a8713..4b97235 100644 --- a/api-reference/openapi.json +++ b/api-reference/openapi.json @@ -993,7 +993,7 @@ "$ref": "#/components/responses/BadRequest" }, "403": { - "$ref": "#/components/responses/Forbidden" + "$ref": "#/components/responses/ForbiddenScoped" }, "404": { "$ref": "#/components/responses/NotFound" @@ -1165,7 +1165,7 @@ "$ref": "#/components/responses/BadRequest" }, "403": { - "$ref": "#/components/responses/Forbidden" + "$ref": "#/components/responses/ForbiddenScoped" }, "404": { "$ref": "#/components/responses/NotFound" @@ -1320,7 +1320,7 @@ "$ref": "#/components/responses/BadRequest" }, "403": { - "$ref": "#/components/responses/Forbidden" + "$ref": "#/components/responses/ForbiddenScoped" }, "404": { "$ref": "#/components/responses/NotFound" @@ -1413,7 +1413,7 @@ "$ref": "#/components/responses/BadRequest" }, "403": { - "$ref": "#/components/responses/Forbidden" + "$ref": "#/components/responses/ForbiddenScoped" }, "404": { "$ref": "#/components/responses/NotFound404DocTransDownload" @@ -2743,7 +2743,7 @@ "$ref": "#/components/responses/BadRequest" }, "403": { - "$ref": "#/components/responses/Forbidden" + "$ref": "#/components/responses/ForbiddenScoped" }, "413": { "$ref": "#/components/responses/PayloadTooLarge" @@ -2872,7 +2872,7 @@ "$ref": "#/components/responses/BadRequest" }, "403": { - "$ref": "#/components/responses/Forbidden" + "$ref": "#/components/responses/ForbiddenScoped" }, "413": { "$ref": "#/components/responses/PayloadTooLarge" @@ -2975,7 +2975,7 @@ "$ref": "#/components/responses/BadRequest" }, "403": { - "$ref": "#/components/responses/Forbidden" + "$ref": "#/components/responses/ForbiddenScoped" }, "404": { "$ref": "#/components/responses/NotFound" @@ -3217,7 +3217,7 @@ "$ref": "#/components/responses/BadRequest" }, "403": { - "$ref": "#/components/responses/Forbidden" + "$ref": "#/components/responses/ForbiddenScoped" }, "404": { "$ref": "#/components/responses/NotFound" @@ -3377,7 +3377,7 @@ "$ref": "#/components/responses/Unauthorized" }, "403": { - "$ref": "#/components/responses/Forbidden" + "$ref": "#/components/responses/ForbiddenScoped" }, "429": { "$ref": "#/components/responses/TooManyRequests" @@ -3593,7 +3593,7 @@ "$ref": "#/components/responses/Unauthorized" }, "403": { - "$ref": "#/components/responses/Forbidden" + "$ref": "#/components/responses/ForbiddenScoped" }, "429": { "$ref": "#/components/responses/TooManyRequests" @@ -3705,7 +3705,7 @@ "$ref": "#/components/responses/Unauthorized" }, "403": { - "$ref": "#/components/responses/Forbidden" + "$ref": "#/components/responses/ForbiddenScoped" }, "429": { "$ref": "#/components/responses/TooManyRequests" @@ -3787,7 +3787,7 @@ "$ref": "#/components/responses/Unauthorized" }, "403": { - "$ref": "#/components/responses/Forbidden" + "$ref": "#/components/responses/ForbiddenScoped" }, "429": { "$ref": "#/components/responses/TooManyRequests" @@ -3903,7 +3903,7 @@ "$ref": "#/components/responses/Unauthorized" }, "403": { - "$ref": "#/components/responses/Forbidden" + "$ref": "#/components/responses/ForbiddenScoped" }, "429": { "$ref": "#/components/responses/TooManyRequests" @@ -3960,7 +3960,7 @@ "$ref": "#/components/responses/Unauthorized" }, "403": { - "$ref": "#/components/responses/Forbidden" + "$ref": "#/components/responses/ForbiddenScoped" }, "404": { "$ref": "#/components/responses/NotFound" @@ -4033,7 +4033,7 @@ "$ref": "#/components/responses/Unauthorized" }, "403": { - "$ref": "#/components/responses/Forbidden" + "$ref": "#/components/responses/ForbiddenScoped" }, "404": { "$ref": "#/components/responses/NotFound" @@ -4084,7 +4084,7 @@ "$ref": "#/components/responses/Unauthorized" }, "403": { - "$ref": "#/components/responses/Forbidden" + "$ref": "#/components/responses/ForbiddenScoped" }, "404": { "$ref": "#/components/responses/NotFound" @@ -4162,7 +4162,7 @@ "$ref": "#/components/responses/Unauthorized" }, "403": { - "$ref": "#/components/responses/Forbidden" + "$ref": "#/components/responses/ForbiddenScoped" }, "404": { "$ref": "#/components/responses/NotFound" @@ -4256,7 +4256,7 @@ "$ref": "#/components/responses/Unauthorized" }, "403": { - "$ref": "#/components/responses/Forbidden" + "$ref": "#/components/responses/ForbiddenScoped" }, "404": { "$ref": "#/components/responses/NotFound" @@ -4325,7 +4325,7 @@ "$ref": "#/components/responses/Unauthorized" }, "403": { - "$ref": "#/components/responses/Forbidden" + "$ref": "#/components/responses/ForbiddenScoped" }, "404": { "$ref": "#/components/responses/NotFound" @@ -4422,7 +4422,7 @@ "$ref": "#/components/responses/Unauthorized" }, "403": { - "$ref": "#/components/responses/Forbidden" + "$ref": "#/components/responses/ForbiddenScoped" }, "404": { "$ref": "#/components/responses/NotFound" @@ -4479,7 +4479,7 @@ "$ref": "#/components/responses/Unauthorized" }, "403": { - "$ref": "#/components/responses/Forbidden" + "$ref": "#/components/responses/ForbiddenScoped" }, "404": { "$ref": "#/components/responses/NotFound" @@ -5405,8 +5405,18 @@ } } }, + "ForbiddenScoped": { + "description": "Authorization failed. Please supply a valid `DeepL-Auth-Key` via the `Authorization` header. This error is also returned when the API key is scoped but does not include the scope required for this endpoint.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, "ForbiddenGlossaries": { - "description": "Forbidden. The access to the requested resource is denied, because of insufficient access rights." + "description": "Forbidden. The access to the requested resource is denied, because of insufficient access rights. This error is also returned when the API key is scoped but does not include the scope required for this endpoint." }, "NotFound": { "description": "The requested resource could not be found.", diff --git a/api-reference/openapi.yaml b/api-reference/openapi.yaml index 2b93a65..c59a8cf 100644 --- a/api-reference/openapi.yaml +++ b/api-reference/openapi.yaml @@ -748,7 +748,7 @@ paths: 400: $ref: '#/components/responses/BadRequest' 403: - $ref: '#/components/responses/Forbidden' + $ref: '#/components/responses/ForbiddenScoped' 404: $ref: '#/components/responses/NotFound' 413: @@ -893,7 +893,7 @@ paths: 400: $ref: '#/components/responses/BadRequest' 403: - $ref: '#/components/responses/Forbidden' + $ref: '#/components/responses/ForbiddenScoped' 404: $ref: '#/components/responses/NotFound' 413: @@ -1015,7 +1015,7 @@ paths: 400: $ref: '#/components/responses/BadRequest' 403: - $ref: '#/components/responses/Forbidden' + $ref: '#/components/responses/ForbiddenScoped' 404: $ref: '#/components/responses/NotFound' 413: @@ -1074,7 +1074,7 @@ paths: 400: $ref: '#/components/responses/BadRequest' 403: - $ref: '#/components/responses/Forbidden' + $ref: '#/components/responses/ForbiddenScoped' 404: $ref: '#/components/responses/NotFound404DocTransDownload' 413: @@ -1931,7 +1931,7 @@ paths: 400: $ref: '#/components/responses/BadRequest' 403: - $ref: '#/components/responses/Forbidden' + $ref: '#/components/responses/ForbiddenScoped' 413: $ref: '#/components/responses/PayloadTooLarge' 415: @@ -2020,7 +2020,7 @@ paths: 400: $ref: '#/components/responses/BadRequest' 403: - $ref: '#/components/responses/Forbidden' + $ref: '#/components/responses/ForbiddenScoped' 413: $ref: '#/components/responses/PayloadTooLarge' 415: @@ -2090,7 +2090,7 @@ paths: 400: $ref: '#/components/responses/BadRequest' 403: - $ref: '#/components/responses/Forbidden' + $ref: '#/components/responses/ForbiddenScoped' 404: $ref: '#/components/responses/NotFound' 413: @@ -2233,7 +2233,7 @@ paths: 400: $ref: '#/components/responses/BadRequest' 403: - $ref: '#/components/responses/Forbidden' + $ref: '#/components/responses/ForbiddenScoped' 404: $ref: '#/components/responses/NotFound' 413: @@ -2344,7 +2344,7 @@ paths: 401: $ref: '#/components/responses/Unauthorized' 403: - $ref: '#/components/responses/Forbidden' + $ref: '#/components/responses/ForbiddenScoped' 429: $ref: '#/components/responses/TooManyRequests' 500: @@ -2509,7 +2509,7 @@ paths: 401: $ref: '#/components/responses/Unauthorized' 403: - $ref: '#/components/responses/Forbidden' + $ref: '#/components/responses/ForbiddenScoped' 429: $ref: '#/components/responses/TooManyRequests' 500: @@ -2583,7 +2583,7 @@ paths: 401: $ref: '#/components/responses/Unauthorized' 403: - $ref: '#/components/responses/Forbidden' + $ref: '#/components/responses/ForbiddenScoped' 429: $ref: '#/components/responses/TooManyRequests' 500: @@ -2635,7 +2635,7 @@ paths: 401: $ref: '#/components/responses/Unauthorized' 403: - $ref: '#/components/responses/Forbidden' + $ref: '#/components/responses/ForbiddenScoped' 429: $ref: '#/components/responses/TooManyRequests' 500: @@ -2711,7 +2711,7 @@ paths: 401: $ref: '#/components/responses/Unauthorized' 403: - $ref: '#/components/responses/Forbidden' + $ref: '#/components/responses/ForbiddenScoped' 429: $ref: '#/components/responses/TooManyRequests' 456: @@ -2746,7 +2746,7 @@ paths: 401: $ref: '#/components/responses/Unauthorized' 403: - $ref: '#/components/responses/Forbidden' + $ref: '#/components/responses/ForbiddenScoped' 404: $ref: '#/components/responses/NotFound' 429: @@ -2791,7 +2791,7 @@ paths: 401: $ref: '#/components/responses/Unauthorized' 403: - $ref: '#/components/responses/Forbidden' + $ref: '#/components/responses/ForbiddenScoped' 404: $ref: '#/components/responses/NotFound' 429: @@ -2823,7 +2823,7 @@ paths: 401: $ref: '#/components/responses/Unauthorized' 403: - $ref: '#/components/responses/Forbidden' + $ref: '#/components/responses/ForbiddenScoped' 404: $ref: '#/components/responses/NotFound' 429: @@ -2871,7 +2871,7 @@ paths: 401: $ref: '#/components/responses/Unauthorized' 403: - $ref: '#/components/responses/Forbidden' + $ref: '#/components/responses/ForbiddenScoped' 404: $ref: '#/components/responses/NotFound' 429: @@ -2932,7 +2932,7 @@ paths: 401: $ref: '#/components/responses/Unauthorized' 403: - $ref: '#/components/responses/Forbidden' + $ref: '#/components/responses/ForbiddenScoped' 404: $ref: '#/components/responses/NotFound' 429: @@ -2975,7 +2975,7 @@ paths: 401: $ref: '#/components/responses/Unauthorized' 403: - $ref: '#/components/responses/Forbidden' + $ref: '#/components/responses/ForbiddenScoped' 404: $ref: '#/components/responses/NotFound' 429: @@ -3038,7 +3038,7 @@ paths: 401: $ref: '#/components/responses/Unauthorized' 403: - $ref: '#/components/responses/Forbidden' + $ref: '#/components/responses/ForbiddenScoped' 404: $ref: '#/components/responses/NotFound' 429: @@ -3074,7 +3074,7 @@ paths: 401: $ref: '#/components/responses/Unauthorized' 403: - $ref: '#/components/responses/Forbidden' + $ref: '#/components/responses/ForbiddenScoped' 404: $ref: '#/components/responses/NotFound' 429: @@ -3668,9 +3668,18 @@ components: application/json: schema: $ref: '#/components/schemas/ErrorResponse' + ForbiddenScoped: + description: Authorization failed. Please supply a valid `DeepL-Auth-Key` via + the `Authorization` header. This error is also returned when the API key is + scoped but does not include the scope required for this endpoint. + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' ForbiddenGlossaries: description: Forbidden. The access to the requested resource is denied, because - of insufficient access rights. + of insufficient access rights. This error is also returned when the API key + is scoped but does not include the scope required for this endpoint. NotFound: description: The requested resource could not be found. content: diff --git a/docs/getting-started/managing-api-keys.mdx b/docs/getting-started/managing-api-keys.mdx index fb39ad1..3eb8e0b 100644 --- a/docs/getting-started/managing-api-keys.mdx +++ b/docs/getting-started/managing-api-keys.mdx @@ -1,12 +1,11 @@ --- title: "Managing API keys" -description: "A guide to creating API keys, getting API key-level usage, and setting API key-level limits." +description: "A guide to creating API keys, getting API key-level usage, setting API key-level limits, and scoping keys to specific endpoints." public: true --- -This page describes managing API keys in the self-admin area. Additionally, an Admin API for API key management is -available to a limited set of Pro API customers. Learn more [here](/api-reference/admin-api). +This page describes managing API keys in the self-admin area. An [Admin API](/api-reference/admin-api) for API key management is also available to all API Growth and API Enterprise subscribers, and to a limited set of Pro API subscribers. ### Get started @@ -65,7 +64,7 @@ Both active and deactivated keys can be renamed. **Copy key** -Copies the API key to your clipboard. For security reasons, we do not show the full key in the table in the “API Keys” tab. Both active and revoked keys can be copied. +Copies the API key to your clipboard. For security reasons, we do not show the full key in the table in the “API Keys & Limits” tab. Both active and revoked keys can be copied. ![](/_assets/images/copy-api-key.png) @@ -103,3 +102,176 @@ It's possible to set an API key-level limit to 0, which means the API key will n ![](/_assets/images/set-key-level-limit-3.png) + +### API key permissions + + + **Private beta.** This feature is currently available to select customers only. To request access, contact your customer success manager or [DeepL support](https://support.deepl.com/hc/en-us/requests/new). + + +API key permissions let you limit what a developer API key can do. Instead of one key with full access to every endpoint, you can issue keys that are scoped to specific operations, for example a key that can only translate text or a key that can only read glossaries. + +Permissions are implemented as scopes. Each scope groups a set of related operations into a single capability you can grant to a key. This section uses "permissions" for the user-facing feature and "scopes" for the technical mechanism. + +Permissions are currently supported only for developer API keys. An account can hold any mix of scoped and unrestricted developer keys. + +**When to use scoped keys** + +| **Key Type** | **Choose When** | +| --- | --- | +| **Unrestricted** | A key can access any endpoint and doesn't need to be limited in any way. | +| **Scoped** | A key should have access only to specific endpoints, for example to prevent glossaries from being modified inadvertently. | + +**How scopes work** + +Developer keys can be turned into scoped keys by assigning them one or more scopes. Once a key is scoped: + +- It can call only the endpoints fully covered by its scopes. +- Every other endpoint returns `403 Forbidden`, including any endpoint that has no scope requirement of its own. +- Some endpoints require more than one scope. A key must hold all of them; if any is missing, the request is denied and the response lists the missing scopes. + +Scopes are enforced only on scoped keys. Unrestricted keys retain full access to every endpoint. Existing keys remain unrestricted by default, but you can assign them scopes at any time. + +**Available scopes** + + + + | **Method** | **Endpoint** | + | --- | --- | + | `POST` | [`/v2/translate`](/api-reference/translate/request-translation) | + + + + | **Method** | **Endpoint** | + | --- | --- | + | `POST` | [`/v2/document`](/api-reference/document/upload-and-translate-a-document) | + | `GET` | [`/v2/document/{document_id}`](/api-reference/document/check-document-status) | + | `GET` | [`/v2/document/{document_id}/result`](/api-reference/document/download-translated-document) | + + + + | **Method** | **Endpoint** | + | --- | --- | + | `POST` | [`/v2/write/rephrase`](/api-reference/improve-text/request-text-improvement) | + | `POST` | [`/v2/write/correct`](/api-reference/improve-text/correct-text) | + + + + | **Method** | **Endpoint** | + | --- | --- | + | `GET` | [`/v3/glossaries`](/api-reference/multilingual-glossaries/list-all-glossaries) | + | `GET` | [`/v3/glossaries/{glossary_id}`](/api-reference/multilingual-glossaries/retrieve-glossary-details) | + | `GET` | [`/v3/glossaries/{glossary_id}/entries`](/api-reference/multilingual-glossaries/retrieve-glossary-entries) | + | `GET` | [`/v2/glossaries`](/api-reference/glossaries/list-all-glossaries) | + | `GET` | [`/v2/glossaries/{glossary_id}`](/api-reference/glossaries/retrieve-glossary-details) | + | `GET` | [`/v2/glossaries/{glossary_id}/entries`](/api-reference/glossaries/retrieve-glossary-entries) | + | `GET` | [`/v2/glossary-language-pairs`](/api-reference/multilingual-glossaries/list-language-pairs-supported-by-glossaries) | + + + + | **Method** | **Endpoint** | + | --- | --- | + | `POST` | [`/v3/glossaries`](/api-reference/multilingual-glossaries/create-a-glossary) | + | `PATCH` | [`/v3/glossaries/{glossary_id}`](/api-reference/multilingual-glossaries/edit-glossary-details) | + | `PUT` | [`/v3/glossaries/{glossary_id}/dictionaries`](/api-reference/multilingual-glossaries/replaces-or-creates-a-dictionary-in-the-glossary-with-the-specified-entries) | + | `DELETE` | [`/v3/glossaries/{glossary_id}`](/api-reference/multilingual-glossaries/delete-a-glossary) | + | `DELETE` | [`/v3/glossaries/{glossary_id}/dictionaries`](/api-reference/multilingual-glossaries/deletes-the-dictionary-associated-with-the-given-language-pair-with-the-given-glossary-id) | + | `POST` | [`/v2/glossaries`](/api-reference/glossaries/create-a-glossary) | + | `DELETE` | [`/v2/glossaries/{glossary_id}`](/api-reference/glossaries/delete-a-glossary) | + + + + | **Method** | **Endpoint** | + | --- | --- | + | `GET` | [`/v3/style_rules`](/api-reference/style-rules/list-all-style-rules) | + | `GET` | [`/v3/style_rules/{style_id}`](/api-reference/style-rules/get-style-rule) | + | `GET` | [`/v3/style_rules/{style_id}/custom_instructions/{instruction_id}`](/api-reference/style-rules/get-custom-instruction) | + + + + | **Method** | **Endpoint** | + | --- | --- | + | `POST` | [`/v3/style_rules`](/api-reference/style-rules/create-style-rule) | + | `PATCH` | [`/v3/style_rules/{style_id}`](/api-reference/style-rules/update-style-rule) | + | `PUT` | [`/v3/style_rules/{style_id}/configured_rules`](/api-reference/style-rules/update-configured-rules) | + | `DELETE` | [`/v3/style_rules/{style_id}`](/api-reference/style-rules/delete-style-rule) | + | `POST` | [`/v3/style_rules/{style_id}/custom_instructions`](/api-reference/style-rules/create-custom-instruction) | + | `PUT` | [`/v3/style_rules/{style_id}/custom_instructions/{instruction_id}`](/api-reference/style-rules/update-custom-instruction) | + | `DELETE` | [`/v3/style_rules/{style_id}/custom_instructions/{instruction_id}`](/api-reference/style-rules/delete-custom-instruction) | + + + + | **Method** | **Endpoint** | + | --- | --- | + | `GET` | [`/v3/translation_memories`](/api-reference/translation-memory/list-translation-memories) | + + + + | **Method** | **Endpoint** | + | --- | --- | + | `GET` | [`/v3/languages`](/api-reference/languages/retrieve-languages-by-resource) | + | `GET` | [`/v3/languages/resources`](/api-reference/languages/retrieve-resources) | + | `GET` | [`/v2/languages`](/api-reference/languages/retrieve-supported-languages) | + + + + | **Method** | **Endpoint** | + | --- | --- | + | `GET` | [`/v2/usage`](/api-reference/usage-and-quota/check-usage-and-limits) | + + + + + **Voice API support is coming.** Permissions don't yet cover the Voice API, so there's no Voice scope to assign. Because a scoped key is blocked from every endpoint its scopes don't cover, a scoped key can't currently reach the Voice API at all. Use an unrestricted key for any workload that needs Voice until Voice scopes ship in a future update. + + +**Create a scoped key** + +Open the ["API Keys & Limits" tab](https://www.deepl.com/your-account/keys) in your account. Click "Create key" and optionally name the key. + +Select **Custom permissions**, then choose one or more scopes from the list. + +Click "Create key" to confirm. The key is created with the chosen scopes and shown once in a popup so you can copy it. + + + + + +**Edit scopes on an existing key** + +You can change scopes on any existing developer API key, including keys that were originally unrestricted. + +In the API keys table, open the key's menu and select "Edit permissions". Choose **All access** to make the key unrestricted, or **Custom permissions** to select specific scopes. Click "Save" to apply. + + + + + + + + + +**Identify scoped and unrestricted keys** + +The API keys table includes a column showing each key's permissions. Hover over the badge to view the assigned scopes. + + +![](/_assets/images/api-key-permissions-badges.png) + + + +![](/_assets/images/api-key-permissions-badge-hover.png) + + +**Error responses** + +When a scoped key calls an endpoint outside its scopes, the API returns a `403 Forbidden` response. The `detail` field lists the scopes the key is missing: + +```json +{ + "message": "Forbidden", + "detail": "Missing required scope(s): glossaries:write" +} +``` + +To resolve it, call an endpoint covered by the key's scopes, or add the missing scope to the key. diff --git a/docs/resources/roadmap-and-release-notes.mdx b/docs/resources/roadmap-and-release-notes.mdx index 7626856..f35cfa3 100644 --- a/docs/resources/roadmap-and-release-notes.mdx +++ b/docs/resources/roadmap-and-release-notes.mdx @@ -6,11 +6,16 @@ rss: true - Support for uploading, modifying, and deleting [translation memories](/docs/learning-how-tos/examples-and-guides/how-to-use-translation-memories) via API -- API key-level endpoint restrictions - Usage reporting by language pair +## June 15 - API Key Permissions (Private Beta) +- Developer API keys can now be scoped to specific endpoints, so a key can be limited to, for example, translating text or reading glossaries. See [API key permissions](/docs/getting-started/managing-api-keys#api-key-permissions). +- Assign one or more scopes when [creating or editing a key](https://www.deepl.com/your-account/keys). A scoped key returns `403 Forbidden` on any endpoint its scopes don't cover. +- Currently in private beta for select customers. To request access, contact your customer success manager or [DeepL support](https://support.deepl.com/hc/en-us/requests/new). +- Not yet supported for the Voice API; Voice scopes will follow in a future update. + ## June 8 - Style Rules and Translation Memories for Document Translation - [`POST /v2/document`](/api-reference/document/upload-and-translate-a-document) now accepts `style_id`, `translation_memory_id`, and `translation_memory_threshold`, bringing document translation in line with the parameters already available on text translation. - `style_id` applies a configured [style rule list](/api-reference/style-rules) to the document translation.