# Attachments API

Upload files and download attachments from messages.

## POST /v1/attachments

**POST** `/v1/attachments`

Upload a file as multipart form data. Max file size: 25 MB. **Requires full-access API key.** Scoped (mailbox-restricted) keys cannot upload attachments.

```curl
curl -X POST https://api.robotomail.com/v1/attachments \
  -H "Authorization: Bearer $ROBOTOMAIL_API_KEY" \
  -F "file=@report.pdf"
```

```response — 201 Created
{
  "id": "a7b8c9d0-bcde-4f01-2345-777777777777",
  "filename": "report.pdf",
  "sizeBytes": 204800
}
```

Include the returned `id` in the `attachments` array when sending a message.

### Errors

- `400` — No file provided
- `403` — Scoped API key (full access required)
- `413` — File exceeds 25 MB or storage limit exceeded


## GET /v1/attachments/:id

**GET** `/v1/attachments/:id`

Get attachment metadata and a presigned download URL (valid for 24 hours). Works for both **outbound** uploads and **inbound** message attachments — inbound attachments are owned by the recipient (the user who owns the receiving mailbox), so the same access check applies. Mailbox-scoped API keys can only access attachments linked to a message in an in-scope mailbox. Unattached uploads return 404 for scoped keys.

For inbound messages with inline images, this endpoint is the REST way to fetch a fresh presigned URL per attachment when rewriting `cid:` URLs in the HTML body. See [Inbound attachments](https://robotomail.com/docs/api/messages#inbound-attachments) for the full inline image rewrite example.

```response — 200 OK
{
  "id": "a7b8c9d0-bcde-4f01-2345-777777777777",
  "messageId": "b2c3d4e5-6789-4abc-def0-222222222222",
  "userId": "u1234",
  "filename": "report.pdf",
  "contentType": "application/pdf",
  "sizeBytes": 204800,
  "r2Key": "u1234/inbound/a7b8c9d0.../report.pdf",
  "contentId": null,
  "createdAt": "2026-04-09T12:00:00.000Z",
  "url": "https://r2.cloudflarestorage.com/...?signed-24h"
}
```

- `contentId` — for inline images, the `Content-ID` header value (without angle brackets). Match this against `cid:foo` references in the HTML body. `null` for normal attachments and outbound uploads.
- `messageId` — the parent `Message` row this attachment is linked to. `null` for orphaned uploads not yet attached to a sent message.
- `r2Key` — internal storage key (returned for backwards compat — do not depend on its shape).
- `url` — presigned R2 URL, valid for 24 hours from the moment of this request. To get a fresh URL after expiry, call this endpoint again.

### Errors

- `402` — `INBOUND_LIMIT_EXCEEDED`: the attachment is linked to an inbound message that was received while the account was over its monthly inbound cap. Outbound uploads and attachments on under-limit inbound messages are unaffected. See [402 `INBOUND_LIMIT_EXCEEDED`](https://robotomail.com/docs/api/errors#inbound-limit-exceeded)
- `404` — Attachment not found, or (for scoped keys) not linked to an in-scope mailbox


## DELETE /v1/attachments/:id

**DELETE** `/v1/attachments/:id`

Delete an attachment. Frees the storage from your account quota. Mailbox-scoped API keys can only delete attachments linked to a message in an in-scope mailbox.

```response — 200 OK
{ "deleted": true }
```


---

Previous: [Domains](https://robotomail.com/docs/api/domains.md) | Next: [Billing](https://robotomail.com/docs/api/billing.md)
