---
title: "Send a document’s bytes"
method: PUT
path: "/api/v1/documents/{id}/content"
tags: ["Files"]
---

# Send a document’s bytes

`PUT /api/v1/documents/{id}/content`

## Request

- Base URL: `http://localhost:5180`
- URL: `http://localhost:5180/api/v1/documents/{id}/content`
- Auth: API key in cookie `better-auth.session_token`

The raw file, as the body, against the key POST minted. The body has to be as long as the upload said and hash to the digest it named, both of which are checked before anything is written, and R2 verifies the same digest again on the way in. The object is written before the row that names it: there is no transaction across the two stores, and an object nothing points at is swept up on a prefix pass, where a row pointing at nothing is a download that fails in front of the owner. Sending the same file twice is the same object once.

## Path parameters

- `id` string, required

## Response `200`

The bytes are on the account, with R2’s own count of them.

- object
  - `ok` unknown, required
  - `data` object, required
    - `documentId` string, required: Stable record id, e.g. `lse_012`.
    - `state` 'pending' | 'stored', required: `pending` once a key is minted and before any bytes land, `stored` once they have. Only a `stored` document can be downloaded.
    - `bytes` integer, required: The size that was claimed while pending, and R2’s own count once stored. A body that is not the claimed length never becomes the second.
    - `contentType` string, required: What the client said the file is while the upload is pending, and what the store holds against the object once it is stored. Nothing anywhere opens the file to check it, so it stays a claim, which is why a download is served as an attachment whatever it says.
    - `checksum` string, required: The digest the object is addressed by, lower-case hex.
    - `checksumAlgo` 'md5' | 'sha256', required: Which digest `checksum` is. R2 answers a single PUT with an MD5 and a multipart upload with a composite ETag, so it is a field rather than an assumption.
    - `uploadedAt` string, nullable, required: ISO 8601 timestamp.
    - `mediaKind` 'photo' | 'video' | 'document', required: What the account calls this file, derived from its content type. A gallery asks for `photo` rather than reading types itself.
    - `maxBytes` integer, required: The largest body this endpoint takes. Read it before streaming: a file over it is refused by the platform, and on some plans that refusal never reaches the API to be explained.
    - `upload` object: Present when the file is too large for one PUT: send it to PUT …/content/parts/{n} instead, then POST …/content/complete.
      - `partBytes` integer, required: Every part but the last is exactly this long.
      - `parts` integer, required: How many parts the declared size splits into.
  - `meta` object, required
    - `requestId` string, required: Echoed in the x-request-id header. Quote it in a bug report.

## Other responses

- `400`: The body was not in the shape this endpoint expects, or the bytes are not the ones the upload declared.
- `401`: No session. Sign in first.
- `404`: No document with that id belongs to you.
- `409`: No upload was started for this document, the one that was started has since been replaced, or the account is holding its 536870912-byte limit and these bytes would not fit.
- `429`: Over the file budget, which is counted per owner and separately from the rest of the API.

---

[API](https://skmtc.dev/dardaris/apis/dardaris-api.md) · [All operations](https://skmtc.dev/dardaris/apis/dardaris-api/llms.txt) · [OpenAPI document](https://skmtc.dev/dardaris/apis/dardaris-api/revisions/4aff33835c73?raw)
