# Attachments

> Every attachments endpoint, generated from the API's own schemas.

Base URL `https://pidgeon.ai/api/v1`. Every request carries `Authorization: Bearer pgn_live_…`. The machine-readable version of this page is https://pidgeon.ai/openapi.json.

## POST /attachments

Upload a file to send

Multipart, with `identity_id` and `file` parts — not base64 in JSON, because a ten-megabyte PDF is thirteen megabytes of base64 and every proxy in between would have to be told. Name the returned id in `attachment_ids` on a send. An upload expires in a day: it is a file no message refers to yet.

**Responses**

- `201` The upload.
- `400` The request is not valid. `error.field` names the input at fault when there is one.
- `401` No API key, or one that is not valid.
- `403` Refused. `error.code` says why: `forbidden` when this key may not do this or the account is on hold, `abuse_blocked` when a limit says no.
- `404` No such resource, or one this key cannot reach. The two answer the same, so treat a 404 as “not available to you” rather than “does not exist”.
- `409` It conflicts with what is there: it already exists, or the `etag` you sent is not the current one.
- `429` Rate limited. `X-RateLimit-Reset` says when to retry.
- `500` Something went wrong on our side. Retry; if it keeps happening, tell us.
- `503` Something this needs is not answering right now. Retry in a moment.

## GET /attachments/{id}

Get a download link for an attachment

A signed URL rather than the bytes: streaming a 25 MB file through the API costs the transfer twice and gains the caller nothing. The link expires.

**Parameters**

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | string | yes |  |

**Responses**

- `200` The link.
- `400` The request is not valid. `error.field` names the input at fault when there is one.
- `401` No API key, or one that is not valid.
- `403` Refused. `error.code` says why: `forbidden` when this key may not do this or the account is on hold, `abuse_blocked` when a limit says no.
- `404` No such resource, or one this key cannot reach. The two answer the same, so treat a 404 as “not available to you” rather than “does not exist”.
- `409` It conflicts with what is there: it already exists, or the `etag` you sent is not the current one.
- `429` Rate limited. `X-RateLimit-Reset` says when to retry.
- `500` Something went wrong on our side. Retry; if it keeps happening, tell us.
- `503` Something this needs is not answering right now. Retry in a moment.

## GET /attachments/{id}/text

Read a text attachment

A CSV, a log, JSON or another text format, as text — up to 5 MB, and the first 200,000 characters of it. Screened by Pidgeon Firewall with the sender of the message it came with, because a file in an email is mail a model reads: `firewall` says whether it tries to steer you, and `body=clean` returns it with hidden characters removed and lines posing as a chat turn marked. HTML, PDF, Office files and images are refused; `GET /attachments/{id}` gives a link to those.

**Parameters**

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | string | yes |  |
| `body` | enum | no |  One of: `original`, `clean`. |

**Responses**

- `200` The text.
- `400` The request is not valid. `error.field` names the input at fault when there is one.
- `401` No API key, or one that is not valid.
- `403` Refused. `error.code` says why: `forbidden` when this key may not do this or the account is on hold, `abuse_blocked` when a limit says no.
- `404` No such resource, or one this key cannot reach. The two answer the same, so treat a 404 as “not available to you” rather than “does not exist”.
- `409` It conflicts with what is there: it already exists, or the `etag` you sent is not the current one.
- `429` Rate limited. `X-RateLimit-Reset` says when to retry.
- `500` Something went wrong on our side. Retry; if it keeps happening, tell us.
- `503` Something this needs is not answering right now. Retry in a moment.
