# Labels and attachments

Two things a thread carries beyond its conversation: tags that are yours to invent, and files that never pass through the API.

## Labels

A label is a free-form tag. Unlike a status it carries no semantics — nothing automates off it, nothing reports on it, and a thread may hold any number at once.

[`support_label_create`](/reference/mcp-tools/support_label_create) adds one to the product dictionary:

```json
{ "name": "billing", "color": "#2563eb" }
```

`name` is one to a hundred characters and unique within the product; a duplicate is refused as a conflict. `color` is an optional six-digit hex string — an uncoloured label is an ordinary label, which is the difference from a status, where the colour is required.

[`support_label_list`](/reference/mcp-tools/support_label_list) returns the dictionary by name. There is nothing to seed: a product that has invented no tags gets an empty list.

There is no way to rename a label, and no delete and no archive. Retiring a label that threads already carry needs the same "say where they go" decision that archiving a status needs, and that has not been built.

### Put labels on a thread

Labels go on at creation, through `labelIds` on [`support_thread_create`](/reference/mcp-tools/support_thread_create), and change through `labelIds` on [`support_thread_update`](/reference/mcp-tools/support_thread_update). Both take up to fifty ids, and every id must name a label of this product — one that does not is reported as not found, and the whole call fails rather than applying part of the set.

`labelIds` replaces the entire set rather than adding to it. To add a label, send the labels the thread already has plus the new one; to strip every label, send `[]`. Wholesale replacement is what makes the call idempotent.

Filtering the queue by `labelIds` narrows it: asking for `billing` and `urgent` returns the threads carrying **both**. Every other filter on [`support_thread_list`](/reference/mcp-tools/support_thread_list) narrows too, and one that widened as you added values would be the odd one out.

## Attachments

The bytes of a file never travel through the Support API. A file is reserved first, uploaded straight to storage through a signed URL, and then confirmed.

### 1. Reserve

[`support_attachment_create`](/reference/mcp-tools/support_attachment_create) reserves the file against a thread:

```json
{
  "threadId": "b50f26b4…",
  "fileName": "screenshot.png",
  "contentType": "image/png",
  "sizeBytes": 184320
}
```

It answers with the attachment row plus an `uploadUrl` and an `uploadExpiresAt`. The URL is short-lived — fifteen minutes — because it is a bearer credential for that one object: whoever ends up holding it, including a log line or a pasted chat message, holds it for exactly that long.

`sizeBytes` is declared up front and capped at 25 MiB, so an oversized upload is refused before a URL is ever issued. `contentType` must be a MIME type, and the URL is signed for exactly that type.

The file name contributes only its base name to where the file is stored; any directory part is discarded, and the storage location is derived by Support from your product and the attachment id — never taken from the caller.

### 2. Upload

`PUT` the bytes to `uploadUrl` with the same `Content-Type` you declared. Storage rejects the upload if the header differs. This request does not go to Support at all.

### 3. Confirm

[`support_attachment_finalize`](/reference/mcp-tools/support_attachment_finalize) takes the attachment id and asks storage whether the object is really there and how large it is. If it is not there the call is refused and the attachment stays pending; if it is larger than the limit the call is refused too, and the declared size is overwritten with the size storage actually reports. On success the attachment becomes `uploaded`, stamped with the moment it was confirmed, and comes back with a `downloadUrl`.

Finalizing again on an already-uploaded attachment is safe: it re-reads nothing and simply hands back a fresh download URL.

Until it is confirmed, an attachment is `pending`: it cannot be named by a message, and it never appears in message output. That is what lets a reader treat every attachment it sees as bytes that exist, rather than as bytes somebody once promised.

### Attach to a message

[`support_message_create`](/reference/mcp-tools/support_message_create) claims attachments through `attachmentIds`, at most ten per message. Each id must

- belong to the same thread as the message,
- be `uploaded`, and
- be unclaimed — a file another message already carries is refused, because moving a file out of the message a reader already saw it under would be a lie about the past.

Naming the same id twice is refused as well, and one bad id refuses the whole message. Partial success is not on offer: a message either carries the files it named or is not written at all.

Once a message is posted, its attachments travel with it. Every read of the message lists them with `fileName`, `contentType`, `sizeBytes` and a freshly signed `downloadUrl` — minted at read time and never stored, so a URL you cached will expire while a re-read always works.

### What Support does not hold

Support holds the record of a file: its name, its declared type, its confirmed size, the thread it belongs to and the message that carries it. The bytes live in the storage bucket the platform provisions for your product, and nothing is ever written to a local disk.

It does not open the file. There is no thumbnailing, no virus scanning, no content-type sniffing and no text extraction — the declared type is what you declared, and Support takes your word for it. There is no delete: an attachment is not detached from its message once claimed, and a reserved attachment that is never uploaded simply stays pending forever.

Reserving and uploading a file is not on the public perimeter, so a message posted there can only name files created and confirmed through the authenticated plane.

## Where to go next

- [Threads and messages](/ship/support/threads-and-messages): the threads and messages these hang off.
- [Tools, SDK and errors](/ship/support/tools-and-errors): every tool named here, and how it is called.
