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

# Upload attachments

> Allocate an Attachment, upload its bytes, confirm completion, and send it in a Message.

An Attachment is a file Relay stores for a message.

Uploading takes four calls. You allocate an Attachment by describing the file, and Relay hands you a signed upload URL. You put the raw bytes there, then send the Attachment ID in a media part.

The Attachment belongs to the agent that allocated it, and only that agent can send it. Keep the Attachment ID; the signed URLs are temporary credentials. For a file already hosted publicly, [Import media from a URL](/messages/import-media) skips the upload.

The upload URL lives 15 minutes. A download URL lives 60 minutes from the moment Relay issues it, and you can ask for a fresh one by ID at any time. [Attachment types and limits](/messages/attachment-types) has the size and metadata rules; the ceiling is 100 MiB.

## Allocate the Attachment

Call `POST /v1/attachments` ([reference](/api-reference/attachments/pre-upload-a-file)) with the file's name, type, and exact size; the SDK method is `relay.attachments.create`. Relay returns `200` with the allocation, and `attachment_id` is the value to keep.

| Field              | Meaning                                                             |
| ------------------ | ------------------------------------------------------------------- |
| `attachment_id`    | The ID you send in a media part                                     |
| `upload_url`       | The signed URL that takes the bytes                                 |
| `http_method`      | Always `PUT`                                                        |
| `expires_at`       | When the upload URL stops working                                   |
| `required_headers` | The exact `Content-Type` and `Content-Length` the upload must carry |

## Upload the bytes

`PUT` the raw bytes to `upload_url` as the whole body, with the required headers unchanged. With curl that is `--data-binary @report.pdf`, and the SDK helper `relay.attachments.upload` applies the headers for you. A successful upload returns `204`.

Base64 and multipart encoding change the body and fail the length check. The signed URL is the credential here; do not send your Agent Token to the upload host.

If the result is uncertain, read the Attachment before you retry. Retry the complete bytes on the same URL only while the Attachment is `pending` and the URL has not expired; after expiry, allocate again.

## Verify the upload

Call `GET /v1/attachments/{attachmentId}` ([reference](/api-reference/attachments/get-attachment-metadata)); the SDK method is `relay.attachments.retrieve`. Send only when `status` is `complete`.

| Status     | Meaning                                              |
| ---------- | ---------------------------------------------------- |
| `pending`  | Relay is waiting for the complete upload             |
| `complete` | The Attachment can be downloaded and sent            |
| `failed`   | Expiry cleanup marked an unfinished upload as failed |

## Send the Attachment

Send a media part with the ID through either [send path](/messages/send):

```json theme={null}
{
  "message": {
    "parts": [{"type":"media","attachment_id":"01993d50-d263-7d6b-87ce-90aba89b7815"}]
  }
}
```

Relay returns `202` with the message. The media part in the response carries the Attachment ID and a signed download URL. Reuse the same idempotency key and body after an uncertain send.

## When it fails

| Step     | Status                                         | Cause                                                          | Next action                                         |
| -------- | ---------------------------------------------- | -------------------------------------------------------------- | --------------------------------------------------- |
| Allocate | `400`                                          | The filename, MIME type, size, or media metadata is invalid.   | Correct the body.                                   |
| Allocate | `413`                                          | `size_bytes` is over 100 MiB (104,857,600 bytes).              | Use a smaller file.                                 |
| Allocate | `415`                                          | The request `Content-Type` is not `application/json`.          | Set the header.                                     |
| Upload   | `400` or `415`                                 | The body length or `Content-Type` differs from the allocation. | Send the exact `required_headers`.                  |
| Upload   | `404`                                          | The URL expired or is wrong.                                   | Read the Attachment, then allocate again if needed. |
| Upload   | `409`                                          | Another upload for the same Attachment is in progress.         | Read the Attachment before retrying.                |
| Send     | `404`, code [2007](/api-reference/errors#2007) | The Attachment is not `complete`.                              | Finish or confirm the upload.                       |

## Next steps

* [Read the types and limits](/messages/attachment-types)
* [Import media from a URL](/messages/import-media)
* [Download attachments](/messages/receiving-media)
* [Delete attachments](/messages/delete-attachments)
