# Uploads

For designers and integrators.

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

## Three steps

1.  [Create an upload](https://public-api.dapi.digital/docs/reference/uploads.md#create_upload) with the file's purpose, name, type and size.
2.  Send the bytes to `upload_url` with the `method` and `headers` it gives.
3.  Reference the upload id in the write that uses the file.

**Upload a design file (Node.js)**

```javascript
import { readFile } from 'node:fs/promises';

const file = await readFile('tracker.3mf');

// 1. Reserve a slot.
const upload = await fetch(`${process.env.DAPI_API_URL}/v2/uploads`, {
  method: 'POST',
  headers: {
    'x-api-key': process.env.DAPI_API_KEY,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    purpose: 'design_file',
    file_name: 'tracker.3mf',
    content_type: 'model/3mf',
    size_bytes: file.byteLength,
  }),
}).then((r) => r.json());

// 2. Send the bytes straight to storage.
await fetch(upload.upload_url, {
  method: upload.method,
  headers: upload.headers,
  body: file,
});

// 3. Use upload.id in the write, for example a new design.
```

**Upload a design file (Python)**

```python
import os
import requests

with open("tracker.3mf", "rb") as f:
    data = f.read()

# 1. Reserve a slot.
upload = requests.post(
    f"{os.environ['DAPI_API_URL']}/v2/uploads",
    headers={"x-api-key": os.environ["DAPI_API_KEY"]},
    json={
        "purpose": "design_file",
        "file_name": "tracker.3mf",
        "content_type": "model/3mf",
        "size_bytes": len(data),
    },
).json()

# 2. Send the bytes straight to storage.
requests.request(
    upload["method"],
    upload["upload_url"],
    headers=upload["headers"],
    data=data,
)

# 3. Use upload["id"] in the write, for example a new design.
```

## Purposes and limits

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

## Expiry

After this the upload URL stops working and the id can no longer be referenced. Uploads not used by then are deleted.

Errors like `upload_expired`, `upload_purpose_mismatch` and `file_too_large` name the problem in `message`.

---

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