# Uploads

Sending files: photos, design files and documents.

## Endpoints

-   [POST `/v2/uploads`](https://public-api.dapi.digital/docs/reference/uploads.md#create_upload)

## The upload object

A slot to upload one file. PUT the bytes to `upload_url`, then reference the id in a write, for example `"images": [{ "upload": "upl_..." }]`.

### Attributes

-   `id` (string): Unique identifier of the upload, prefixed with `upl_`. Treat it as an opaque string: store it and compare it, never parse it.
-   `object` (string): Names the type of this object. Always `upload`.
-   `purpose` (UploadPurpose):
    
    What the file is for. Each purpose accepts its own types and size:
    
    -   `extra_image`: a photo of an extra part; image/png, image/jpeg, image/webp; file names ending in .png, .jpg, .jpeg, .jfif, .webp (or no extension); up to 25,000,000 bytes (25 MB).
    -   `product_image`: a product or variant photo; image/png, image/jpeg, image/webp; file names ending in .png, .jpg, .jpeg, .jfif, .webp (or no extension); up to 25,000,000 bytes (25 MB).
    -   `design_file`: a printable model (.3mf, .stl or .obj); model/3mf, model/stl, model/obj, application/octet-stream; file names ending in .3mf, .stl, .obj; up to 150,000,000 bytes (150 MB).
    -   `order_file`: a model printed for one order line instead of its design (.3mf or .stl); only for accounts with custom files on; model/3mf, model/stl, application/sla, application/vnd.ms-pki.stl, application/octet-stream; file names ending in .3mf, .stl; up to 1,073,741,824 bytes (1024 MiB).
    -   `attachment`: a production document for the farm (PDF, image or short video); application/pdf, image/png, image/jpeg, video/mp4; file names ending in .pdf, .png, .jpg, .jpeg, .jfif, .mp4, .m4v (or no extension); up to 25,000,000 bytes (25 MB).
    -   `qc_photo`: a photo for a QC message or issue report; image/png, image/jpeg, image/webp; file names ending in .png, .jpg, .jpeg, .jfif, .webp (or no extension); up to 25,000,000 bytes (25 MB).
    -   `compliance_document`: a product safety document (PDF or image); application/pdf, image/png, image/jpeg; file names ending in .pdf, .png, .jpg, .jpeg, .jfif (or no extension); up to 25,000,000 bytes (25 MB).
    -   `quote_file`: a model to quote (.stl, .3mf, .obj, .step or .stp); integrators only; model/stl, model/3mf, model/obj, model/step, application/step, application/sla, application/vnd.ms-pki.stl, application/octet-stream; file names ending in .stl, .3mf, .obj, .step, .stp; up to 262,144,000 bytes (250 MiB).
    -   `withdrawal_invoice`: an invoice (PDF, in USD) for a withdrawal request; integrators only; application/pdf; file names ending in .pdf; up to 10,485,760 bytes (10 MiB). The extension must match `content_type`. When a write uses the upload, its contents are checked against that type (the first bytes of an image, PDF or video; the header and structure of a model), and a file that is not what it claims is refused with 422 `invalid_file`.
    
-   `upload_url` (string): Send the file bytes here, with `method` and `headers` exactly as given.
-   `method` (string): The HTTP method for sending the file to `upload_url`. Always `PUT`.
-   `headers` (object): Headers the upload request must carry.
-   `expires_at` (timestamp): After this the upload URL stops working and the id can no longer be referenced. Uploads not used by then are deleted.

**The upload object**

```json
{
  "id": "upl_bfP0GSqKoZFrD72H5Os0rc7HV9XP6eQJu",
  "object": "upload",
  "purpose": "extra_image",
  "upload_url": "https://files.example.com/temp/7f3a.png?signature=abc",
  "method": "PUT",
  "headers": {
    "Content-Type": "image/png"
  },
  "expires_at": "2026-10-15T10:00:00Z"
}
```

## Start a file upload

POST `/v2/uploads`

- **Credentials**: An API key with the `products:write`, `extras:write`, `stocks:write`, `qc:write` or `quotes:write` scope (full access includes every scope). A signed-in dashboard session works too.

- **For**: Designers and Integrators

- **Operation id**: `create_upload`

Reserves a slot for one file and returns where to send it. Files go straight to storage, never through the API: (1) call this, (2) PUT the bytes to `upload_url` with the given headers, (3) reference the returned id in the write that uses the file. A type, file name extension (missing, not accepted for the purpose, or not matching `content_type`) or size the purpose does not allow is refused here, before anything is uploaded. The contents are checked when a write uses the upload (422 `invalid_file`). Designers may use every purpose except `quote_file` and `withdrawal_invoice`; integrators may use `quote_file` and `withdrawal_invoice` (with the `quotes:write` scope) and `extra_image` (with the `extras:write` scope).

### Headers

-   `Idempotency-Key` (string): Any unique string, for example a UUID. Sending the same request again with the same key within 24 hours returns the first response without doing anything twice. Reusing a key with a different body is refused with `idempotency_key_reused`. A refused request (4xx) frees the key so you can correct and resend it; a server error (5xx) is kept and returned again, because the write may already have happened: check the resource before retrying with a new key.

### Request body

-   `purpose` (UploadPurpose, required): Same shape as `UploadPurpose` above.
-   `file_name` (string, required): The file name, kept for display. Its extension must be one the purpose accepts and match `content_type`; model files and invoices need one.
-   `content_type` (string, required): MIME type of the file. Must be allowed for the purpose.
-   `size_bytes` (integer, required): Exact size of the file in bytes. Must be within the purpose limit (see `purpose`; 1 MB is 1,000,000 bytes and 1 MiB is 1,048,576 bytes).
    
    Range at least 1.
    

### Returns 201 Created

The upload slot. Returns [the upload object](https://public-api.dapi.digital/docs/reference/uploads.md#upload_object).

### Responses

-   `201`: The upload slot.
-   `400`: The request is invalid. `details.errors` lists each problem (`validation_failed`), or an `expand[]` path is unknown (`invalid_expand`).
-   `401`: Credentials are missing (`missing_credentials`), the API key is unknown or inactive (`invalid_api_key`), the session expired (`invalid_session`), or both a key and a session were sent (`ambiguous_credentials`).
-   `403`: The purpose is not available to your kind of account (`purpose_not_allowed`), a scope is missing (`missing_scope`), or the purpose needs a signed-in dashboard session (`credential_not_allowed`: `withdrawal_invoice`, like withdrawals themselves).
-   `422`: The type, extension or size is not allowed for the purpose (`file_type_not_allowed`, `file_too_large`).
-   `429`: Too many requests (`rate_limited`): the limit is 500 per minute per API key (or signed-in user), plus 60 per minute per API key (or signed-in user) for quote, upload, order and payment operations. Wait for the number of seconds in the `Retry-After` header, then retry; `details` has `limit`, `window_seconds`, `reset_at` and `retry_after_seconds`.

Errors use [the error object](https://public-api.dapi.digital/docs/guides/errors.md#error-object).

**Request**

```bash
curl -X POST "https://public-api.dapi.digital/v2/uploads" \
  -H "x-api-key: $DAPI_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "purpose": "design_file",
  "file_name": "tracker.3mf",
  "content_type": "model/3mf",
  "size_bytes": 2758723
}'
```

---

3DAPI API Docs. This page on the web: https://public-api.dapi.digital/docs/reference/uploads. Index of every page: https://public-api.dapi.digital/docs/llms.txt.
