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 00000000..f71acdbb
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 00000000..2c6bd2ce
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 00000000..0d30ddcc
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 00000000..d209d7ac
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 00000000..8063465c
Binary files /dev/null and b/_assets/images/api-key-permissions-edit-menu.png differ
diff --git a/api-reference/admin-api/api-key-permissions.mdx b/api-reference/admin-api/api-key-permissions.mdx
new file mode 100644
index 00000000..ad2e1a12
--- /dev/null
+++ b/api-reference/admin-api/api-key-permissions.mdx
@@ -0,0 +1,183 @@
+---
+title: "API Key Permissions"
+description: "Learn how admins can restrict an API key's access to specific endpoints."
+public: true
+---
+
+export const ScopeHeader = ({label, scope}) => (
+
+ {label}
+ {scope}
+
+)
+
+API key permissions let admins 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 page 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.
+
+### 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 admins can assign them scopes at any time.
+
+
+### Available scopes
+
+
+
+
+ }>
+
+
+
+ }>
+
+
+
+ }>
+
+
+
+ }>
+
+
+
+ }>
+
+
+
+ }>
+
+
+
+ }>
+
+
+
+ }>
+
+
+
+ }>
+
+
+
+ }>
+
+
+
+
+### Create a scoped key
+
+Open the [Keys](https://www.deepl.com/your-account/keys) page 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.
+
+
+
+

+

+
+
+
+### Error responses
+
+When a scoped key calls an endpoint outside its scopes, the API returns a `403 Forbidden` response. To resolve it, call an endpoint covered by the key's scopes, or add the missing scope to the key.
diff --git a/api-reference/admin-api/managing-admin-keys.mdx b/api-reference/admin-api/managing-admin-keys.mdx
index 6a820f04..a6ab608e 100644
--- a/api-reference/admin-api/managing-admin-keys.mdx
+++ b/api-reference/admin-api/managing-admin-keys.mdx
@@ -1,5 +1,5 @@
---
-title: "Managing admin API keys"
+title: "Managing Admin API Keys"
description: "Learn how admins can create and manage admin API keys."
public: true
---
diff --git a/api-reference/openapi.json b/api-reference/openapi.json
index 35a87130..4b972354 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 2b93a65d..c59a8cfe 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.json b/docs.json
index c6dc8cd0..3207c7b9 100644
--- a/docs.json
+++ b/docs.json
@@ -299,6 +299,7 @@
],
"drilldown": false
},
+ "api-reference/admin-api/api-key-permissions",
{
"group": "Usage & Quota",
"pages": [