Docsary
Markdown Ask Claude Ask ChatGPT
On this page

Images and assets

Images, diagrams, PDFs, and downloads are uploaded through the API and served from /assets/<path> on your site's hostname.

Assets belong to the site, not to a version. Every version of a site shares one asset namespace, so a diagram referenced from three versions is uploaded once.

Uploading

The upload endpoint takes the file as a raw request body. The path is the URL path, given after /assets/:

curl -s -X POST "$DOCSARY_API/v1/sites/acme/assets/img/architecture.png" \
  -H "authorization: Bearer $DOCSARY_KEY" \
  -H "content-type: image/png" \
  --data-binary @architecture.png
{ "path": "img/architecture.png", "size": 48213, "sha256": "9f2c..." }

Set content-type explicitly. It is stored verbatim and served back on every request for that asset. When the header is missing, the asset is stored as application/octet-stream, and browsers will download it instead of displaying it. curl will not guess for you.

Posting to a path that already exists replaces the object and updates its recorded size and hash.

Referencing from markdown

![Architecture](/assets/img/architecture.png)

*The request path, end to end.*

Images are fitted to the content width, so there is no width to set. A caption is a line of italic text under the image — HTML in a page renders as literal text, so <figure> and <figcaption> will not work; see HTML in markdown.

Always reference assets by absolute path. Relative image paths are emitted verbatim and will break on any page that is not at the depth you assumed.

Constraints

Constraint Value
Maximum size 20 MB per asset (413 above that)
Total storage Your organization's asset storage ceiling (413)
Path characters letters, digits, ., _, -, /
Path start must begin with a letter or digit
Path traversal .. anywhere in the path is rejected
Empty body rejected with 400

There is no image processing. No resizing, no format conversion, no thumbnail generation, no responsive srcset. What you upload is what is served. Compress and size images before uploading them.

Caching and filenames

Assets are served with cache-control: public, max-age=31536000, immutable.

That is a one-year cache with an explicit promise that the bytes at this URL will never change. It makes repeat page loads very fast, and it has one consequence you have to design around:

Re-uploading different content to the same asset path will not reach visitors who have already loaded it. Their browser keeps the old file for up to a year, and no publish or purge on our side changes that — the promise was made to the reader's browser, not to a cache we control.

So: when an asset's content changes, give it a new path. The reliable pattern is a content-derived filename:

/assets/img/architecture-9f2c1a.png
/assets/img/architecture-4d81e7.png     ← the updated diagram

Update the markdown to point at the new name, push both, and delete the old asset once nothing references it. Overwriting in place is safe only for an asset that has never been served.

Listing and deleting

curl -s -H "authorization: Bearer $DOCSARY_KEY" \
  "$DOCSARY_API/v1/sites/acme/assets"
{
  "assets": [
    { "path": "img/architecture.png", "content_type": "image/png", "size": 48213, "sha256": "9f2c..." }
  ]
}
curl -s -X DELETE "$DOCSARY_API/v1/sites/acme/assets/img/architecture.png" \
  -H "authorization: Bearer $DOCSARY_KEY"

Deleting an asset removes both the stored file and its record. Nothing checks whether a page still references it, so a delete can leave a broken image. Check /assets against your markdown before pruning.

Deleting a site removes its assets too — the records and the stored files both go, and the delete response reports how many files were removed. Nothing of yours is left behind billing storage, and nothing survives to be served afterwards. There is no undo, so export anything you want to keep first.