# Authentication and test mode

> Authenticate with API keys, choose scopes, use test mode, roll and revoke keys, and find the API host for your data.



Every API request is authenticated with an API key that belongs to one workspace. Keys have a mode (test or live), a set of scopes and optional restrictions. Create and manage them under **API keys** in the dashboard at [https://app.dynamicdocumentapi.com](https://app.dynamicdocumentapi.com).

## Send your API key [#send-your-api-key]

Pass the key as a bearer token in the `Authorization` header:

```bash
curl https://api-eu.dynamicdocumentapi.com/v1/account \
  -H "Authorization: Bearer $DYNAMIC_DOCUMENT_API_KEY"
```

Tools that can only set a custom header can send the key in `X-API-Key` instead:

```bash
curl https://api-eu.dynamicdocumentapi.com/v1/account \
  -H "X-API-Key: $DYNAMIC_DOCUMENT_API_KEY"
```

A request without credentials fails with `401 authentication_required`, and an unknown or malformed key fails with `401 invalid_api_key`. OAuth 2.0 access tokens for marketplace apps and integrations are planned.

## Key format [#key-format]

Keys have this structure:

```text
dda_live_<24-character key ID>_<40-character secret>
dda_test_<24-character key ID>_<40-character secret>
```

The mode is part of the key, so you can tell test and live keys apart at a glance.

Only a hash of the secret is stored. The full key is displayed once, when you create it; afterwards the dashboard shows only the prefix and the last four characters. If you lose a key, roll it or create a new one. Creating a key requires you to confirm your identity again.

## Keys, scopes and restrictions [#keys-scopes-and-restrictions]

A workspace can have any number of keys, for example one per service or environment. Owners and Admins manage all keys in a workspace, and Developers manage their own keys. Each key has:

* a **name**, so you can tell what it's used for
* a **mode**: `test` or `live`
* **scopes** that limit what it can do
* an optional **template allowlist**
* an optional **IP allowlist**
* an optional **expiry date**

### Scopes [#scopes]

Give each key only the scopes it needs. A request outside the key's scopes fails with `403 insufficient_scope`.

| Scope                                     | Allows                                                                                                                                                                                              |
| ----------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `render:write`                            | Creating renders with `POST /v1/renders` and the `/v1/pdf/…` and `/v1/images/…` convenience endpoints, as well as [batches](/docs/batches), [PDF tools](/docs/pdf-tools), e-invoice XML and uploads |
| `renders:read`                            | Listing and retrieving renders, batches and email sends, with their files and logs                                                                                                                  |
| `renders:delete`                          | Purging render files with `DELETE /v1/renders/{id}/files`                                                                                                                                           |
| `templates:read`                          | Listing templates and reading their schemas, versions and sources                                                                                                                                   |
| `templates:write`                         | Creating templates, replacing drafts, publishing, rolling back and deleting templates through the API                                                                                               |
| `webhooks:read`, `webhooks:write`         | Reading webhook endpoints and deliveries; managing endpoints and replaying deliveries                                                                                                               |
| `storage:read`, `storage:write`           | Reading [storage destinations](/docs/storage); creating, changing, testing and deleting them                                                                                                        |
| `signed_links:read`, `signed_links:write` | Reading [signed links](/docs/signed-links); creating, changing, signing and deleting them                                                                                                           |
| `email:read`, `email:write`               | Reading the connections and rules of [email delivery](/docs/email-delivery); creating, changing, testing and deleting them                                                                          |
| `account:read`                            | Reading the account, plan, limits and usage (`GET /v1/account`, `GET /v1/usage`)                                                                                                                    |

For webhooks, storage, signed links and email, the `write` scope includes reading: a key with only `storage:write` can also list and retrieve storage destinations.

### Template allowlist [#template-allowlist]

Restrict a key to specific templates, for example a key used by a no-code tool that should only generate one document type. Rendering any other template fails with `403 template_not_allowed_for_key`.

### IP allowlist [#ip-allowlist]

Restrict a key to one or more IPv4 or IPv6 ranges in CIDR notation, such as `203.0.113.0/24`. Requests from other addresses fail with `403 ip_not_allowed`. To restrict every key of the workspace, set the **API IP allowlist** under **Settings** → **Security** in the dashboard. It works the same way, and a key with its own allowlist has to pass both.

### Expiry [#expiry]

Keys can expire on a date you choose. You receive an email before a key expires, and requests with an expired key fail with `401 api_key_expired`. An `api_key.expiring` webhook event is planned.

## Test mode [#test-mode]

Test keys let you build and run automated tests against the real API without using any of your renders:

|                    | Test key                                                                                 | Live key                                                                      |
| ------------------ | ---------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------- |
| Prefix             | `dda_test_`                                                                              | `dda_live_`                                                                   |
| Billed renders     | Free; not counted in usage                                                               | Billed per the [render table](/docs/plans-and-limits#what-counts-as-a-render) |
| Output             | Watermarked "TEST" (e-invoice XML: a [TEST note](/docs/e-invoicing#test-mode))           | Clean                                                                         |
| File storage       | Deleted after 24 hours                                                                   | Kept for your workspace's retention period                                    |
| Webhooks           | Delivered and signed                                                                     | Delivered and signed                                                          |
| Rate limit         | Test renders per minute by plan: 10 (Free), 60 (Starter, Growth), 120 (Pro), 300 (Scale) | Your plan's [rate limits](/docs/plans-and-limits#limits-by-plan)              |
| Template drafts    | `"version": "draft"` allowed                                                             | Only if your workspace settings allow it                                      |
| Email verification | Not required                                                                             | Required (`403 email_not_verified`)                                           |

Renders made with a test key are always test renders and have `"test": true`. Sending `"test": true` with a live key fails with `403 test_key_required`; use a test key instead.

Use test keys in development, CI and staging. Test renders go through the same pipeline as live renders, so layouts, page counts, errors and webhooks match live behaviour apart from the watermark.

## Roll and revoke keys [#roll-and-revoke-keys]

Rotate keys regularly and whenever someone who had access leaves.

* **Roll** a key to create a new secret with the same name, mode, scopes and restrictions. Choose a grace period of 0, 1, 24 or 72 hours during which the old secret keeps working, deploy the new key, and let the old one lapse.
* **Revoke** a key to disable it immediately. Requests with a revoked key fail with `401 api_key_revoked`.

The dashboard shows when each key was last used, the last IP address and request counts for the past 24 hours and 30 days. Keys that haven't been used for 90 days are flagged so you can remove them.

> **If a key leaks**
>
> Roll it with a grace period of 0 hours, or revoke it, then deploy the replacement. Review recent renders in the dashboard for requests you don't recognise.

## Never use secret keys in browsers [#never-use-secret-keys-in-browsers]

CORS is not enabled for secret API keys, so browsers can't call the API directly, and a key embedded in a web page, mobile app or public repository can be copied by anyone who sees it. Call the API from your backend and pass the resulting file or signed URL to the client.

To let a page or an email request an image or PDF without a key, use [signed links](/docs/signed-links): URLs signed on your server with a per-link secret. Publishable keys that can only create signed URLs for allowlisted templates and origins are planned.

## Data residency [#data-residency]

Render payloads and generated files are processed and stored in the EU. The API answers on two hosts:

| Host                                    | Region       |
| --------------------------------------- | ------------ |
| `https://api-eu.dynamicdocumentapi.com` | EU (default) |
| `https://api.dynamicdocumentapi.com`    | EU (alias)   |

Append `/v1` to the host to get the base URL, for example `https://api-eu.dynamicdocumentapi.com/v1`.

## Request IDs [#request-ids]

Every response has an `X-Request-Id` header, and error responses repeat it in the `request_id` field. Log it with your own request data, and include it when you contact support at [support@dynamicdocumentapi.com](mailto:support@dynamicdocumentapi.com). You can also send your own `X-Request-Id` header to correlate API calls with your logs.
