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

# Labels

> Create, rename, recolour and delete your organization's shared labels, and put them on messages.

A label has two halves that live in different places, and knowing which is
which explains everything else on this page.

The **keyword** is an IMAP flag on the message itself. It is what makes a label
real: apply one here and it is visible in Thunderbird, on a phone, anywhere the
mailbox is opened. The **name and colour** are a row in Ruber's database — the
catalogue that says what that keyword is called.

**Required permission:** Labels → Read for listing, Labels → Write to create,
rename or delete. Putting a label *on a message* is
[`messages:write`](/api/messages#update-a-message), because that changes the
message, not the catalogue.

<Note>
  **These are organization labels, not personal ones.**

  Labels in the Ruber web app belong to a person: two people in one organization
  keep different catalogues and neither sees the other's. An API key has no
  person, so it gets its own kind — a label owned by the organization and
  readable by every member.

  A key therefore **cannot see, rename, delete or apply anybody's personal
  labels**. A key leaked from a CI job should not reveal how a colleague files
  their mail. Organization labels created here do show up for your team in the
  web app, which is the point: an integration's label has to be nameable in the
  interface where someone reads the message.
</Note>

## List labels

```
GET https://app.ruber.me/api/v1/labels
```

```json theme={null}
{
  "data": [
    {
      "id": "377a839e-acf0-453b-b4c6-34f4c85975ae",
      "name": "Invoices",
      "keyword": "Invoices",
      "colour": "#3b82f6",
      "created_at": "2026-09-07T11:53:59.267Z"
    }
  ]
}
```

Takes no parameters — labels belong to the organization, not to a mailbox.

`keyword` is what you pass to
[`add_labels` / `remove_labels`](/api/messages#update-a-message). `id` is what
you pass to the two endpoints below.

## Create a label

```
POST https://app.ruber.me/api/v1/labels
```

```bash theme={null}
curl -X POST https://app.ruber.me/api/v1/labels \
  -H "Authorization: Bearer rk_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{"name": "Invoices", "colour": "#3b82f6"}'
```

| Field    |                                                                                                                       |
| -------- | --------------------------------------------------------------------------------------------------------------------- |
| `name`   | **required.** Up to 32 characters. Unique within the organization.                                                    |
| `colour` | A hex code like `#3b82f6`, or one of `slate`, `red`, `amber`, `green`, `blue`, `violet`, `pink`. Defaults to `slate`. |

`201 Created`, returning the label including its `keyword`.

<Note>
  **You do not choose the keyword.** It is derived from the name by the database,
  so every caller produces the same one, and it is checked against every keyword
  already used in the organization — including members' personal labels. A label
  named `Invoices` in an organization where somebody already uses that keyword
  becomes `Invoices-2`.

  That collision check matters: a keyword is an IMAP flag, so two labels sharing
  one are the same label as far as the mailbox is concerned. An integration
  tagging "Invoices" would otherwise silently light up a colleague's personal
  filter.
</Note>

## Rename or recolour

```
PATCH https://app.ruber.me/api/v1/labels/{id}
```

```bash theme={null}
curl -X PATCH https://app.ruber.me/api/v1/labels/377a839e-acf0-453b-b4c6-34f4c85975ae \
  -H "Authorization: Bearer rk_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{"name": "Supplier invoices", "colour": "green"}'
```

Send at least one of `name` or `colour`.

<Warning>
  **A rename does not change the `keyword`, and that is deliberate.**

  The keyword is a flag sitting on every message already tagged. Changing it
  would mean rewriting flags across the whole mailbox to keep those messages
  labelled — slow, not atomic, and visible to every other client mid-flight.

  So renaming is a one-row update, tagged messages stay tagged, and the keyword
  you stored stays valid. Do not derive the keyword from the name in your own
  code; read it from the API.
</Warning>

## Delete a label

```
DELETE https://app.ruber.me/api/v1/labels/{id}
```

```json theme={null}
{
  "deleted": true,
  "id": "377a839e-acf0-453b-b4c6-34f4c85975ae",
  "keyword_left_on_messages": "Invoices"
}
```

<Note>
  **Deleting a label leaves the keyword on the messages.** The name and colour
  go; the flag stays.

  Stripping the flag from every tagged message would be a mailbox-wide rewrite
  triggered by one DELETE, and destructive in a way nobody asked for — a keyword
  set by somebody's desktop client belongs to them, not to our row about it.

  The practical consequence is good: recreate a label with the same name and it
  recovers the same keyword, so the messages light up again. An accidental delete
  is survivable.
</Note>

## Putting a label on a message

That is a message change, so it lives on the message endpoint and needs
`messages:write`:

```bash theme={null}
curl -X PATCH "https://app.ruber.me/api/v1/messages/55?mailbox=you@yourdomain.com&folder=inbox" \
  -H "Authorization: Bearer rk_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{"add_labels": ["Invoices"], "remove_labels": ["Unsorted"]}'
```

Both take **keywords**, not names, and every keyword must be one of your
organization's labels — an unknown one is refused rather than written. Without
that check `add_labels` would write arbitrary atoms onto a message as IMAP
flags, which the mailbox would then carry forever with nothing able to name
them.

See [Update a message](/api/messages#update-a-message) for the full endpoint.

## Errors

| Code              | HTTP  | Cause                                                                               |
| ----------------- | ----- | ----------------------------------------------------------------------------------- |
| `invalid_request` | `400` | Missing or too-long `name`, a bad `colour`, a duplicate name, or nothing to change. |
| `unauthorized`    | `401` | Missing, malformed, unknown or revoked key.                                         |
| `forbidden`       | `403` | The key lacks `labels:read` or `labels:write`.                                      |
| `not_found`       | `404` | No organization label with that id. A personal label answers the same way.          |
| `rate_limited`    | `429` | Over 600 requests in a minute.                                                      |

See [Errors](/api/introduction#errors) for the full response shape.
