# Dynamic Document API: Storage

> Your own S3/Azure/GCS buckets for render output (BYOS).

> Part of the Dynamic Document API documentation. Index of all pages for agents: [llms.txt](https://dynamicdocumentapi.com/llms.txt), every page in one file: [llms-full.txt](https://dynamicdocumentapi.com/llms-full.txt).

6 endpoints of the [API reference](https://dynamicdocumentapi.com/docs/api-reference/index.md), version 1.0.0. Interactive reference: https://dynamicdocumentapi.com/docs/api-reference#tag/storage. Machine-readable OpenAPI document: https://dynamicdocumentapi.com/openapi/openapi-public.json

## Servers

- https://api-eu.dynamicdocumentapi.com — EU (Germany)

## Endpoints

- `GET /v1/storage-destinations` — List storage destinations
- `POST /v1/storage-destinations` — Create a storage destination
- `GET /v1/storage-destinations/{destination_id}` — Retrieve a storage destination
- `PATCH /v1/storage-destinations/{destination_id}` — Update a storage destination
- `DELETE /v1/storage-destinations/{destination_id}` — Delete a storage destination
- `POST /v1/storage-destinations/{destination_id}/test` — Test a storage destination

## GET /v1/storage-destinations

**List storage destinations**

Your own buckets that renders can be uploaded to. Credentials are never returned.

Responses:

- `200` Every object in the workspace (a single page today).
- `401` Missing, invalid, expired or revoked credentials.
- `403` Insufficient scope, IP not allowed, workspace suspended or scheduled for deletion, or email not verified.
- `429` Rate limited (`Retry-After`).
- `503` The control plane is unreachable (`Retry-After`).

## POST /v1/storage-destinations

**Create a storage destination**

`credentials` are encrypted at rest and never returned; reads answer `{"configured": true}`. Reference the destination from `delivery.storage[]` on a render. On Amazon S3, `config.storage_class` sets the class of every uploaded object and of the connection test's probe: `STANDARD`, `STANDARD_IA`, `ONEZONE_IA`, `INTELLIGENT_TIERING` or `GLACIER_IR`; leave it out for the bucket's default. Archive classes (`GLACIER`, `DEEP_ARCHIVE`) and classes on other providers answer `400`. `config.force_path_style` (S3 and S3-compatible) puts the bucket in the path (`true`, `<endpoint>/<bucket>/<key>`) or in the host name (`false`, `<bucket>.<endpoint host>`); left out, it is `false` for Amazon S3 and `true` for S3-compatible services. Bucket names with dots, underscores or capitals and IP-address endpoints always use the path. Uploaded objects get the file's content type.

Parameters:

| Name | In | Type | Description |
|---|---|---|---|
| `Idempotency-Key` | header | string | Replays the stored response for 24 h; a different body with the same key → 422. |

Request body (`application/json`, required): StorageDestinationWrite

| Field | Type | Description |
|---|---|---|
| `config` (required) | object |  |
| `credentials` | object | Never returned again; send it only when it changes. |
| `is_default` | boolean |  |
| `keep_hosted_copy` | boolean |  |
| `name` (required) | string |  |
| `path_template` | string |  |
| `provider` (required) | "s3" \| "s3_compatible" \| "azure_blob" \| "gcs" \| "sftp" | * `s3` - Amazon S3 * `s3_compatible` - S3-compatible * `azure_blob` - Azure Blob Storage * `gcs` - Google Cloud Storage * `sftp` - SFTP |

Responses:

- `201` 
- `400` Invalid request body (`errors[]` with JSON pointers).
- `401` Missing, invalid, expired or revoked credentials.
- `403` Insufficient scope, IP not allowed, workspace suspended or scheduled for deletion, or email not verified.
- `429` Rate limited (`Retry-After`).
- `503` The control plane is unreachable (`Retry-After`).

## GET /v1/storage-destinations/{destination_id}

**Retrieve a storage destination**

Common base: API key or OAuth only, never a dashboard region token.

Parameters:

| Name | In | Type | Description |
|---|---|---|---|
| `destination_id` (required) | path | string | Storage destination id (`dst_…`). |

Responses:

- `200` 
- `401` Missing, invalid, expired or revoked credentials.
- `403` Insufficient scope, IP not allowed, workspace suspended or scheduled for deletion, or email not verified.
- `404` Storage destination not found.
- `429` Rate limited (`Retry-After`).
- `503` The control plane is unreachable (`Retry-After`).

## PATCH /v1/storage-destinations/{destination_id}

**Update a storage destination**

Partial update. `provider` is immutable — create a new destination to change it. Stored credentials are only sent to the server they were entered for: a `config` that changes the `endpoint`, the Azure `account`, or the SFTP `host`, `port`, `username` or `host_key` needs `credentials` in the same request, otherwise the request is refused (400, `/credentials`).

Parameters:

| Name | In | Type | Description |
|---|---|---|---|
| `destination_id` (required) | path | string | Storage destination id (`dst_…`). |

Request body (`application/json`): PatchedStorageDestinationWrite

| Field | Type | Description |
|---|---|---|
| `config` | object |  |
| `credentials` | object | Never returned again; send it only when it changes. |
| `is_default` | boolean |  |
| `keep_hosted_copy` | boolean |  |
| `name` | string |  |
| `path_template` | string |  |
| `provider` | "s3" \| "s3_compatible" \| "azure_blob" \| "gcs" \| "sftp" | * `s3` - Amazon S3 * `s3_compatible` - S3-compatible * `azure_blob` - Azure Blob Storage * `gcs` - Google Cloud Storage * `sftp` - SFTP |

Responses:

- `200` 
- `400` Invalid request body (`errors[]` with JSON pointers).
- `401` Missing, invalid, expired or revoked credentials.
- `403` Insufficient scope, IP not allowed, workspace suspended or scheduled for deletion, or email not verified.
- `404` Storage destination not found.
- `429` Rate limited (`Retry-After`).
- `503` The control plane is unreachable (`Retry-After`).

## DELETE /v1/storage-destinations/{destination_id}

**Delete a storage destination**

Files already uploaded to your bucket are untouched. Uploads that haven't succeeded yet stop: each fails at its next attempt, without connecting, with `storage.upload_failed`.

Parameters:

| Name | In | Type | Description |
|---|---|---|---|
| `destination_id` (required) | path | string | Storage destination id (`dst_…`). |

Responses:

- `204` Deleted.
- `401` Missing, invalid, expired or revoked credentials.
- `403` Insufficient scope, IP not allowed, workspace suspended or scheduled for deletion, or email not verified.
- `404` Storage destination not found.
- `429` Rate limited (`Retry-After`).
- `503` The control plane is unreachable (`Retry-After`).

## POST /v1/storage-destinations/{destination_id}/test

**Test a storage destination**

Writes, reads back and deletes a small probe object from the region that owns the workspace. `200` with `ok: false` means the credentials or the bucket policy are wrong — `steps[]` says which check failed. The result is also stored as `last_test`.

Parameters:

| Name | In | Type | Description |
|---|---|---|---|
| `Idempotency-Key` | header | string | Replays the stored response for 24 h; a different body with the same key → 422. |
| `destination_id` (required) | path | string | Storage destination id (`dst_…`). |

Responses:

- `200` The probe ran; check `ok`.
- `400` Invalid request body (`errors[]` with JSON pointers).
- `401` Missing, invalid, expired or revoked credentials.
- `403` Insufficient scope, IP not allowed, workspace suspended or scheduled for deletion, or email not verified.
- `404` Storage destination not found.
- `429` Rate limited (`Retry-After`).
- `503` The control plane is unreachable (`Retry-After`).
