Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added _assets/images/api-key-permissions-badges.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added _assets/images/api-key-permissions-create.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added _assets/images/api-key-permissions-edit-menu.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
183 changes: 183 additions & 0 deletions api-reference/admin-api/api-key-permissions.mdx

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@sejla I think this page belongs in the "Admin" section of the "Documentation" tab rather than "API Reference" tab, since it's not actually an API spec. Does that make sense? It could also be a section or sub-page of "Managing API Keys", if not a standalone page: https://deepl-c950b784-ae-485-api-key-permissions.mintlify.app/docs/getting-started/managing-api-keys

Original file line number Diff line number Diff line change
@@ -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}) => (
<span style={{display: 'flex', justifyContent: 'space-between', alignItems: 'center', width: '100%', gap: '1rem'}}>
<span>{label}</span>
<code style={{fontWeight: 400, fontSize: '0.8125rem', backgroundColor: '#e2e8f0', color: '#475569', padding: '0.125rem 0.5rem', borderRadius: '0.375rem'}}>{scope}</code>
</span>
)

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.

<Warning>
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.
</Warning>

### Available scopes

<style>{`
.scope-endpoints code { font-size: 1rem; }
`}</style>

<AccordionGroup>
<Accordion title={<ScopeHeader label="Translate text" scope="translate:text" />}>
<table className="scope-endpoints">
<tbody>
<tr><td><code>POST</code></td><td><a href="/api-reference/translate/request-translation"><code>/v2/translate</code></a></td></tr>
</tbody>
</table>
</Accordion>

<Accordion title={<ScopeHeader label="Translate document" scope="translate:document" />}>
<table className="scope-endpoints">
<tbody>
<tr><td><code>POST</code></td><td><a href="/api-reference/document/upload-and-translate-a-document"><code>/v2/document</code></a></td></tr>
<tr><td><code>GET</code></td><td><a href="/api-reference/document/check-document-status"><code>/v2/document/&#123;document_id&#125;</code></a></td></tr>
<tr><td><code>GET</code></td><td><a href="/api-reference/document/download-translated-document"><code>/v2/document/&#123;document_id&#125;/result</code></a></td></tr>
</tbody>
</table>
</Accordion>

<Accordion title={<ScopeHeader label="Rephrase or correct text" scope="write:improve" />}>
<table className="scope-endpoints">
<tbody>
<tr><td><code>POST</code></td><td><a href="/api-reference/improve-text/request-text-improvement"><code>/v2/write/rephrase</code></a></td></tr>
<tr><td><code>POST</code></td><td><a href="/api-reference/improve-text/correct-text"><code>/v2/write/correct</code></a></td></tr>
</tbody>
</table>
</Accordion>

<Accordion title={<ScopeHeader label="View glossaries and their entries" scope="glossaries:read" />}>
<table className="scope-endpoints">
<tbody>
<tr><td><code>GET</code></td><td><a href="/api-reference/multilingual-glossaries/list-all-glossaries"><code>/v3/glossaries</code></a></td></tr>
<tr><td><code>GET</code></td><td><a href="/api-reference/multilingual-glossaries/retrieve-glossary-details"><code>/v3/glossaries/&#123;glossary_id&#125;</code></a></td></tr>
<tr><td><code>GET</code></td><td><a href="/api-reference/multilingual-glossaries/retrieve-glossary-entries"><code>/v3/glossaries/&#123;glossary_id&#125;/entries</code></a></td></tr>
<tr><td><code>GET</code></td><td><a href="/api-reference/glossaries/list-all-glossaries"><code>/v2/glossaries</code></a></td></tr>
<tr><td><code>GET</code></td><td><a href="/api-reference/glossaries/retrieve-glossary-details"><code>/v2/glossaries/&#123;glossary_id&#125;</code></a></td></tr>
<tr><td><code>GET</code></td><td><a href="/api-reference/glossaries/retrieve-glossary-entries"><code>/v2/glossaries/&#123;glossary_id&#125;/entries</code></a></td></tr>
<tr><td><code>GET</code></td><td><a href="/api-reference/multilingual-glossaries/list-language-pairs-supported-by-glossaries"><code>/v2/glossary-language-pairs</code></a></td></tr>
</tbody>
</table>
</Accordion>

<Accordion title={<ScopeHeader label="Create, modify, and delete glossaries" scope="glossaries:write" />}>
<table className="scope-endpoints">
<tbody>
<tr><td><code>POST</code></td><td><a href="/api-reference/multilingual-glossaries/create-a-glossary"><code>/v3/glossaries</code></a></td></tr>
<tr><td><code>PATCH</code></td><td><a href="/api-reference/multilingual-glossaries/edit-glossary-details"><code>/v3/glossaries/&#123;glossary_id&#125;</code></a></td></tr>
<tr><td><code>PUT</code></td><td><a href="/api-reference/multilingual-glossaries/replaces-or-creates-a-dictionary-in-the-glossary-with-the-specified-entries"><code>/v3/glossaries/&#123;glossary_id&#125;/dictionaries</code></a></td></tr>
<tr><td><code>DELETE</code></td><td><a href="/api-reference/multilingual-glossaries/delete-a-glossary"><code>/v3/glossaries/&#123;glossary_id&#125;</code></a></td></tr>
<tr><td><code>DELETE</code></td><td><a href="/api-reference/multilingual-glossaries/deletes-the-dictionary-associated-with-the-given-language-pair-with-the-given-glossary-id"><code>/v3/glossaries/&#123;glossary_id&#125;/dictionaries</code></a></td></tr>
<tr><td><code>POST</code></td><td><a href="/api-reference/glossaries/create-a-glossary"><code>/v2/glossaries</code></a></td></tr>
<tr><td><code>DELETE</code></td><td><a href="/api-reference/glossaries/delete-a-glossary"><code>/v2/glossaries/&#123;glossary_id&#125;</code></a></td></tr>
</tbody>
</table>
</Accordion>

<Accordion title={<ScopeHeader label="View style rules and custom instructions" scope="style_rules:read" />}>
<table className="scope-endpoints">
<tbody>
<tr><td><code>GET</code></td><td><a href="/api-reference/style-rules/list-all-style-rules"><code>/v3/style_rules</code></a></td></tr>
<tr><td><code>GET</code></td><td><a href="/api-reference/style-rules/get-style-rule"><code>/v3/style_rules/&#123;style_id&#125;</code></a></td></tr>
<tr><td><code>GET</code></td><td><a href="/api-reference/style-rules/get-custom-instruction"><code>/v3/style_rules/&#123;style_id&#125;/custom_instructions/&#123;instruction_id&#125;</code></a></td></tr>
</tbody>
</table>
</Accordion>

<Accordion title={<ScopeHeader label="Create, modify, and delete style rules and custom instructions" scope="style_rules:write" />}>
<table className="scope-endpoints">
<tbody>
<tr><td><code>POST</code></td><td><a href="/api-reference/style-rules/create-style-rule"><code>/v3/style_rules</code></a></td></tr>
<tr><td><code>PATCH</code></td><td><a href="/api-reference/style-rules/update-style-rule"><code>/v3/style_rules/&#123;style_id&#125;</code></a></td></tr>
<tr><td><code>PUT</code></td><td><a href="/api-reference/style-rules/update-configured-rules"><code>/v3/style_rules/&#123;style_id&#125;/configured_rules</code></a></td></tr>
<tr><td><code>DELETE</code></td><td><a href="/api-reference/style-rules/delete-style-rule"><code>/v3/style_rules/&#123;style_id&#125;</code></a></td></tr>
<tr><td><code>POST</code></td><td><a href="/api-reference/style-rules/create-custom-instruction"><code>/v3/style_rules/&#123;style_id&#125;/custom_instructions</code></a></td></tr>
<tr><td><code>PUT</code></td><td><a href="/api-reference/style-rules/update-custom-instruction"><code>/v3/style_rules/&#123;style_id&#125;/custom_instructions/&#123;instruction_id&#125;</code></a></td></tr>
<tr><td><code>DELETE</code></td><td><a href="/api-reference/style-rules/delete-custom-instruction"><code>/v3/style_rules/&#123;style_id&#125;/custom_instructions/&#123;instruction_id&#125;</code></a></td></tr>
</tbody>
</table>
</Accordion>

<Accordion title={<ScopeHeader label="Retrieve translation memories" scope="translation_memories:read" />}>
<table className="scope-endpoints">
<tbody>
<tr><td><code>GET</code></td><td><a href="/api-reference/translation-memory/list-translation-memories"><code>/v3/translation_memories</code></a></td></tr>
</tbody>
</table>
</Accordion>

<Accordion title={<ScopeHeader label="Retrieve languages and resources" scope="languages:read" />}>
<table className="scope-endpoints">
<tbody>
<tr><td><code>GET</code></td><td><a href="/api-reference/languages/retrieve-languages-by-resource"><code>/v3/languages</code></a></td></tr>
<tr><td><code>GET</code></td><td><a href="/api-reference/languages/retrieve-resources"><code>/v3/languages/resources</code></a></td></tr>
<tr><td><code>GET</code></td><td><a href="/api-reference/languages/retrieve-supported-languages"><code>/v2/languages</code></a></td></tr>
</tbody>
</table>
</Accordion>

<Accordion title={<ScopeHeader label="View subscription usage and limits" scope="usage:read" />}>
<table className="scope-endpoints">
<tbody>
<tr><td><code>GET</code></td><td><a href="/api-reference/usage-and-quota/check-usage-and-limits"><code>/v2/usage</code></a></td></tr>
</tbody>
</table>
</Accordion>
</AccordionGroup>

### 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.

<Frame caption="Creating a scoped API key">
<img src="/_assets/images/api-key-permissions-create.png" style={{width: '100%', maxWidth: '340px'}} />
</Frame>

### 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.

<Frame caption="Editing scopes for an existing API key">
<div style={{display: 'flex', flexDirection: 'column', gap: '2.5rem', alignItems: 'center'}}>
<img src="/_assets/images/api-key-permissions-edit-menu.png" style={{width: '100%', maxWidth: '200px'}} />
<img src="/_assets/images/api-key-permissions-edit-dialog.png" style={{width: '100%', maxWidth: '480px'}} />
</div>
</Frame>

### 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.

<Frame caption="Viewing keys and their scopes">
<div style={{display: 'flex', flexDirection: 'column', gap: '2.5rem'}}>
<img src="/_assets/images/api-key-permissions-badges.png" style={{width: '100%'}} />
<img src="/_assets/images/api-key-permissions-badge-hover.png" style={{width: '100%'}} />
</div>
</Frame>

### 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.
2 changes: 1 addition & 1 deletion api-reference/admin-api/managing-admin-keys.mdx
Original file line number Diff line number Diff line change
@@ -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
---
Expand Down
54 changes: 32 additions & 22 deletions api-reference/openapi.json
Original file line number Diff line number Diff line change
Expand Up @@ -993,7 +993,7 @@
"$ref": "#/components/responses/BadRequest"
},
"403": {
"$ref": "#/components/responses/Forbidden"
"$ref": "#/components/responses/ForbiddenScoped"
},
"404": {
"$ref": "#/components/responses/NotFound"
Expand Down Expand Up @@ -1165,7 +1165,7 @@
"$ref": "#/components/responses/BadRequest"
},
"403": {
"$ref": "#/components/responses/Forbidden"
"$ref": "#/components/responses/ForbiddenScoped"
},
"404": {
"$ref": "#/components/responses/NotFound"
Expand Down Expand Up @@ -1320,7 +1320,7 @@
"$ref": "#/components/responses/BadRequest"
},
"403": {
"$ref": "#/components/responses/Forbidden"
"$ref": "#/components/responses/ForbiddenScoped"
},
"404": {
"$ref": "#/components/responses/NotFound"
Expand Down Expand Up @@ -1413,7 +1413,7 @@
"$ref": "#/components/responses/BadRequest"
},
"403": {
"$ref": "#/components/responses/Forbidden"
"$ref": "#/components/responses/ForbiddenScoped"
},
"404": {
"$ref": "#/components/responses/NotFound404DocTransDownload"
Expand Down Expand Up @@ -2743,7 +2743,7 @@
"$ref": "#/components/responses/BadRequest"
},
"403": {
"$ref": "#/components/responses/Forbidden"
"$ref": "#/components/responses/ForbiddenScoped"
},
"413": {
"$ref": "#/components/responses/PayloadTooLarge"
Expand Down Expand Up @@ -2872,7 +2872,7 @@
"$ref": "#/components/responses/BadRequest"
},
"403": {
"$ref": "#/components/responses/Forbidden"
"$ref": "#/components/responses/ForbiddenScoped"
},
"413": {
"$ref": "#/components/responses/PayloadTooLarge"
Expand Down Expand Up @@ -2975,7 +2975,7 @@
"$ref": "#/components/responses/BadRequest"
},
"403": {
"$ref": "#/components/responses/Forbidden"
"$ref": "#/components/responses/ForbiddenScoped"
},
"404": {
"$ref": "#/components/responses/NotFound"
Expand Down Expand Up @@ -3217,7 +3217,7 @@
"$ref": "#/components/responses/BadRequest"
},
"403": {
"$ref": "#/components/responses/Forbidden"
"$ref": "#/components/responses/ForbiddenScoped"
},
"404": {
"$ref": "#/components/responses/NotFound"
Expand Down Expand Up @@ -3377,7 +3377,7 @@
"$ref": "#/components/responses/Unauthorized"
},
"403": {
"$ref": "#/components/responses/Forbidden"
"$ref": "#/components/responses/ForbiddenScoped"
},
"429": {
"$ref": "#/components/responses/TooManyRequests"
Expand Down Expand Up @@ -3593,7 +3593,7 @@
"$ref": "#/components/responses/Unauthorized"
},
"403": {
"$ref": "#/components/responses/Forbidden"
"$ref": "#/components/responses/ForbiddenScoped"
},
"429": {
"$ref": "#/components/responses/TooManyRequests"
Expand Down Expand Up @@ -3705,7 +3705,7 @@
"$ref": "#/components/responses/Unauthorized"
},
"403": {
"$ref": "#/components/responses/Forbidden"
"$ref": "#/components/responses/ForbiddenScoped"
},
"429": {
"$ref": "#/components/responses/TooManyRequests"
Expand Down Expand Up @@ -3787,7 +3787,7 @@
"$ref": "#/components/responses/Unauthorized"
},
"403": {
"$ref": "#/components/responses/Forbidden"
"$ref": "#/components/responses/ForbiddenScoped"
},
"429": {
"$ref": "#/components/responses/TooManyRequests"
Expand Down Expand Up @@ -3903,7 +3903,7 @@
"$ref": "#/components/responses/Unauthorized"
},
"403": {
"$ref": "#/components/responses/Forbidden"
"$ref": "#/components/responses/ForbiddenScoped"
},
"429": {
"$ref": "#/components/responses/TooManyRequests"
Expand Down Expand Up @@ -3960,7 +3960,7 @@
"$ref": "#/components/responses/Unauthorized"
},
"403": {
"$ref": "#/components/responses/Forbidden"
"$ref": "#/components/responses/ForbiddenScoped"
},
"404": {
"$ref": "#/components/responses/NotFound"
Expand Down Expand Up @@ -4033,7 +4033,7 @@
"$ref": "#/components/responses/Unauthorized"
},
"403": {
"$ref": "#/components/responses/Forbidden"
"$ref": "#/components/responses/ForbiddenScoped"
},
"404": {
"$ref": "#/components/responses/NotFound"
Expand Down Expand Up @@ -4084,7 +4084,7 @@
"$ref": "#/components/responses/Unauthorized"
},
"403": {
"$ref": "#/components/responses/Forbidden"
"$ref": "#/components/responses/ForbiddenScoped"
},
"404": {
"$ref": "#/components/responses/NotFound"
Expand Down Expand Up @@ -4162,7 +4162,7 @@
"$ref": "#/components/responses/Unauthorized"
},
"403": {
"$ref": "#/components/responses/Forbidden"
"$ref": "#/components/responses/ForbiddenScoped"
},
"404": {
"$ref": "#/components/responses/NotFound"
Expand Down Expand Up @@ -4256,7 +4256,7 @@
"$ref": "#/components/responses/Unauthorized"
},
"403": {
"$ref": "#/components/responses/Forbidden"
"$ref": "#/components/responses/ForbiddenScoped"
},
"404": {
"$ref": "#/components/responses/NotFound"
Expand Down Expand Up @@ -4325,7 +4325,7 @@
"$ref": "#/components/responses/Unauthorized"
},
"403": {
"$ref": "#/components/responses/Forbidden"
"$ref": "#/components/responses/ForbiddenScoped"
},
"404": {
"$ref": "#/components/responses/NotFound"
Expand Down Expand Up @@ -4422,7 +4422,7 @@
"$ref": "#/components/responses/Unauthorized"
},
"403": {
"$ref": "#/components/responses/Forbidden"
"$ref": "#/components/responses/ForbiddenScoped"
},
"404": {
"$ref": "#/components/responses/NotFound"
Expand Down Expand Up @@ -4479,7 +4479,7 @@
"$ref": "#/components/responses/Unauthorized"
},
"403": {
"$ref": "#/components/responses/Forbidden"
"$ref": "#/components/responses/ForbiddenScoped"
},
"404": {
"$ref": "#/components/responses/NotFound"
Expand Down Expand Up @@ -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.",
Expand Down
Loading
Loading