> ## Documentation Index
> Fetch the complete documentation index at: https://docs.wristler.eu/llms.txt
> Use this file to discover all available pages before exploring further.

# Sending us a file

> Two ways in, one handle out, and the two places to name it.

A file arrives in two steps, because those are two different requests: the file
itself is bytes, and what it belongs to is a JSON:API document. So you send the
file first, get a **handle** back, and name that handle in the thing the file
belongs to.

```
POST /v1/uploads   →   handle   →   imageHandles on a listing
                                    attachmentHandles on a message
```

Until it is named, the file belongs to nobody.

## Two ways in

> [tab] The bytes
>
> `multipart/form-data`, with a single `file` part. What every HTTP client on
> every platform can send without help, and what an app with a photograph from a
> camera has.
>
> ```bash theme={null}
> curl -X POST https://api.wristler.eu/v1/uploads \
>   -H "Authorization: Bearer $TOKEN" \
>   -H "Accept: application/vnd.api+json" \
>   -F file=@front.jpg
> ```

> [tab] An address
>
> An ordinary JSON:API document with a `sourceUrl`, and we fetch it. For anything
> that already hosts its files — a shop, a PIM, a system you are moving away from.
>
> ```bash theme={null}
> curl -X POST https://api.wristler.eu/v1/uploads \
>   -H "Authorization: Bearer $TOKEN" \
>   -H "Content-Type: application/vnd.api+json" \
>   -H "Accept: application/vnd.api+json" \
>   -d '{"data":{"type":"pending-uploads","attributes":{"sourceUrl":"https://your-shop.example/front.jpg"}}}'
> ```
>
> We fetch it while the request is open, so one file is one call. If we cannot
> reach it, the answer is `502` with code `upload_failed` — the same as a storage
> service that will not take a file, and for the same reason: nothing you did, and
> worth retrying.

Both answer the same thing:

```json theme={null}
{
  "data": {
    "type": "pending-uploads",
    "id": "8812",
    "attributes": {
      "handle": "/wristler-image-storage/front-20260923.jpeg",
      "fileType": "image",
      "createdAt": "2026-09-23T09:14:02+00:00"
    }
  }
}
```

**handle** `string`

The storage service's own path for the file, and the only name either of us has
for it. This is what you send back.

**fileType** `string`

`image` or `video`, worked out from the file.

There is no `self` link and no `Location`: a pending upload has no address to read
it back at. It is written once, named once, and then it is an attachment.

## Naming it

> [card] [On a listing](https://docs.wristler.eu/guides/images.md)
>
> `imageHandles`, up to 8, in the order the gallery should be in.

> [card] [On a message](https://docs.wristler.eu/app/messaging.md)
>
> `attachmentHandles`, up to 4.

Nothing after the upload knows which road the file came in by, so a chat
attachment can come from a URL as easily as from a camera: fetch it into a
handle, then name the handle on the message.

## What we take

Images and video up to **10 MB**: `jpg`, `jpeg`, `png`, `gif`, `webp`, `heic`,
`heif`, `mp4`, `mov`, `m4v`. Anything else is `422`.

Scope `uploads:write`.

> [!WARNING]
>
> **A whole gallery is the one case not to use this for.** Eight photographs is
> eight calls, each waiting on a fetch. Give the listing `imageSources` instead —
> the URLs, all at once — and we fetch the set in the background and skip it
> entirely when it has not changed since the last sync. One call, and the second
> run costs nothing.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.