> For the complete documentation index, see [llms.txt](https://docs.tiny.plus/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.tiny.plus/api/endpoints/attachments.md).

# Attachments

Images and documents on a record: [list them](#images-and-documents-on-a-record), [open a document](#open-a-document), and [attach a file](#attach-a-file).

## Images and documents on a record

<mark style="color:blue;">`GET`</mark> `https://api.tiny.plus/v2/attachments?related_id={{id}}`

Lists what the record's Images & Docs panel lists in tiny+, in the same order.

```bash
curl "https://api.tiny.plus/v2/attachments?related_id=66" \
  -H "Authorization: Bearer {{user_access_token}}"
```

```json
{
    "related_id": "66",
    "flavour": "projects",
    "allow_images": true,
    "allow_documents": true,
    "perm_edit": true,
    "images": [
        {
            "id": "2352",
            "name": "Ashgrove-Golf-Club-retirement-village-render.jpg",
            "description": null,
            "credit": null,
            "thumb_url": "https://….blob.core.windows.net/account-3/thumbs/e8ef5be8fa2ef4444656c0259276d179.jpg",
            "full_url": "https://….blob.core.windows.net/account-3/images/e8ef5be8fa2ef4444656c0259276d179.jpg",
            "width": 2048,
            "height": 1318,
            "is_default": true,
            "created_date": "2020-01-06T02:27:37Z"
        }
    ],
    "documents": [
        {
            "id": "1292",
            "name": "a primer on tiny 20170314.pdf",
            "description": null,
            "doctype": "pdf",
            "created_date": "2019-11-29T03:34:58Z"
        }
    ]
}
```

* `allow_images` and `allow_documents` say which the record type keeps. Companies and contacts keep documents only, so their `images` is always empty.
* Images are in order of name, then id, and documents in order of name.
* `thumb_url` is a 200×200 crop and `full_url` the whole image. Both are lasting URLs.
* `is_default` marks the record's main picture.
* A document carries no URL. Ask for one when it's opened: see below.
* `perm_edit` says whether the token's user may attach files to the record.
* On an account with user docs switched on, `user_docs` lists the record's user docs, each with `id`, `name` and `record_url`.
* Dates are in UTC. `use_cache=0` reads past the cache.

| Case                                             | Response                                           |
| ------------------------------------------------ | -------------------------------------------------- |
| `related_id` missing or not a number             | `400 {"error": "Must specify a valid record id."}` |
| No such record, or a type the API doesn't serve  | `404 {"error": "Record not found"}`                |
| A deleted record                                 | `410 {"error": "This record has been deleted."}`   |
| A confidential record the token's user can't see | `403 {"error": "This record is confidential."}`    |

## Open a document

<mark style="color:blue;">`GET`</mark> `https://api.tiny.plus/v2/attachments/{{id}}`

Gives a link that opens a document, or an image, for the next ten minutes.

```bash
curl "https://api.tiny.plus/v2/attachments/10592" \
  -H "Authorization: Bearer {{user_access_token}}"
```

```json
{
    "id": "10592",
    "name": "Arcadia Terms and Conditions.pdf",
    "doctype": "pdf",
    "url": "https://….blob.core.windows.net/account-3/docs/08d5c1641948fd3786653ce910eb5b2e.pdf?st=…&se=…&sig=…",
    "expires_at": "2026-10-02T04:03:49Z"
}
```

* The link needs no token, so a browser can open it. It opens the file under its own name and type: a PDF opens as a PDF.
* It stops working at `expires_at`, in UTC. Ask again rather than keeping it.
* Only a file on at least one record the token's user can see has a link. Anything else, including a file whose stored copy is missing, answers `404 {"error": "File not found."}`.

## Attach a file

<mark style="color:green;">`POST`</mark> `https://api.tiny.plus/v2/attachments`

Attaches a file — an image or a document — to a record.

```bash
curl -X POST "https://api.tiny.plus/v2/attachments" \
  -H "Authorization: Bearer {{user_access_token}}" \
  -H "Content-Type: application/json" \
  -d '{
        "related_id": "1343",
        "attachments": {
            "data": "iVBORw0KGgoAAAANSUhEUgAA…",
            "mime_type": "image/png",
            "name": "site-plan.png"
        }
      }'
```

```json
["161140"]
```

### The request

| Field         | Description                                                                                        |
| ------------- | -------------------------------------------------------------------------------------------------- |
| `related_id`  | Required. The record to attach the file to. It must be a record the token's user can see and edit. |
| `attachments` | Required. One file as an object, or several as an array of objects.                                |

Each file object has:

| Field       | Description                                                                                                                             |
| ----------- | --------------------------------------------------------------------------------------------------------------------------------------- |
| `data`      | The file's bytes, base64-encoded. Send the base64 on its own, not a `data:…` URL.                                                       |
| `mime_type` | The file's type, such as `image/png` or `application/pdf`. It decides whether the file is stored as an image or a document — see below. |
| `name`      | The file name. Its extension is used in the stored file's path.                                                                         |

### Images and documents

The `mime_type` decides how the file is kept:

* **`image/png`, `image/jpeg` and `image/gif`** are stored as **images**: re-encoded, rotated upright, and given a thumbnail. An animated GIF keeps its animation. An image attached to a record becomes that record's main picture.
* **`image/webp`, `image/avif`, `image/bmp`, `image/heic`, `image/heif`, `image/tiff` and `image/svg+xml`** are stored as **images** too, converted: to a PNG if any of the picture is transparent, and to a JPEG if not. The file keeps the `name` it was sent with. A HEIC or TIFF larger than 4096 pixels on its longest side is scaled down to that, and only the first page of a TIFF is kept. An SVG is drawn 2048 pixels on its longest side. A file that can't be read, such as an animated WebP or an SVG that refers to other files, is skipped.
* **Everything else** is stored as a **document**, unchanged.

### The response

`200` with an array of the new file ids, one per file that was stored, as strings. Attaching the same image bytes twice returns the existing image's id and links it to the record again.

Where the file goes in tiny+:

* An **image** appears in the record's Images panel and becomes its main picture. On a project or event, it's also posted to the News Feed, unless that record is confidential.
* A **document** appears in the record's Documents panel.
* On a **News Feed post** or a **comment**, the file is part of it, as one attached in tiny+ is: it's in the post's `images` or `documents` (or the comment's) in [`GET /v2/updates`](/api/endpoints/updates.md), and nothing else is posted about it.

Nobody is notified. The new file is in the record's [images and documents](#images-and-documents-on-a-record) straight away.

### Errors

| Case                                                                                                                 | Response                                                                              |
| -------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------- |
| A [read-only token](/api/concepts/permissions.md#read-only-tokens)                                                   | `403 {"error": "This token is read-only."}`                                           |
| `related_id` missing, not a number, deleted, in another account, or a confidential record the token's user can't see | `400 {"error": "Must specify a valid record id."}`                                    |
| A record the token's user can't edit                                                                                 | `403 {"error": "You may not edit this record: it is not one you own or administer."}` |
| A post that isn't the token's user's own, unless they're a News Feed admin                                           | `403 {"error": "You may not change this post: it is not yours."}`                     |
| `attachments` missing, or not an object or array                                                                     | `400 {"error": "Attachments data not valid."}`                                        |

Nothing is stored when the request is refused.

{% hint style="warning" %}
A file object that's missing `data`, `mime_type` or `name`, or whose bytes aren't the image its `mime_type` names, is skipped rather than reported. Check the returned array has an id for each file you sent.
{% endhint %}
