# Batch

> One request, up to 1.000 messages, one outcome per id: archive, read state, labels and delete.

Source: https://developer.mueslimail.com/docs/batch/

`POST /v0/messages/batch` applies one action to many messages in a single transaction: `add_labels`, `remove_labels`, `archive`, `mark_read`, `mark_unread` or `delete`. It exists for the agent that sorts a hundred messages after a rule fires, and for the one that clears a mailbox: one call, one request against the rate limit, one transaction, and a line of answer for every id.

## One outcome per id

`results` has a line for every id you sent, in the order you sent it, with `status` and, when the action did not apply, `error`: `applied`, or `not_found` (no such message in your organization, or one held by screening), or `forbidden` (a delete your key may not perform), or `invalid` (not a message UUID and not an RFC Message-ID). `applied` and `failed` count lines, so the two always add up to what you sent, duplicates included.

> Note: One bad id never fails the call. A batch of 1.000 ids where a hundred are missing answers `200` with a hundred `not_found` lines and applies the other nine hundred; a `5xx` here means the request itself broke, never one of its items.

```bash
curl -s -X POST https://api.mueslimail.com/v0/messages/batch \
  -H "Authorization: Bearer ***" \
  -H "Content-Type: application/json" \
  -d '{"ids": ["<message-uuid>", "<message-uuid>"], "action": "archive"}'
```

## The two permissions

* `add_labels`, `remove_labels`, `archive`, `mark_read` and `mark_unread` need `message_update`, the same permission the single message `PATCH` requires. Without it the whole call is `403 missing_permission`, before any message is touched.
* `delete` needs `data_delete`, the destructive permission of the organization: a full access key never implies it, so an agent key asking to delete finds `forbidden` on every item and no mail is removed. Issue a key that lists `data_delete` for the one job that erases mail.
* `unread` is the read state and `archived` is the archive state: `mark_read` removes `unread`, `mark_unread` adds it, `archive` adds `archived`.

## Retries are safe

Every action is idempotent: sending the same batch twice reports `applied` twice and changes nothing the second time, a label is never duplicated, and deleting an id that is already gone answers `not_found` for that line instead of an error. `ids` holds at most 1.000 entries; a longer list is a `400 validation_error` with the number you sent, and an action outside the six above is a `422 unknown_action`.

## Endpoints

### POST /v0/messages/batch

Apply one action to up to 1.000 messages, with one outcome per id (requires message_update, or data_delete for a delete)

Body:

| Name | Type | Notes |
| --- | --- | --- |
| `ids` | string[] | required, Message UUIDs or RFC Message-IDs, at most 1.000; a repeated id is answered once per occurrence and applied once, at least 1 item, at most 1000 items |
| `action` | add_labels \| remove_labels \| archive \| mark_read \| mark_unread \| delete | required |
| `labels` | string[] | Required by add_labels and remove_labels, refused by the other four |

Responses:

* `200` One outcome per id, whatever they were: BatchResult
* `400` Error envelope: Error
* `403` Error envelope: Error
* `422` Error envelope: Error

### BatchResult object

| Name | Type | Notes |
| --- | --- | --- |
| `applied` | integer | Lines whose action happened (or had already happened: every action is idempotent) |
| `failed` | integer | Lines with a per item failure: not_found, forbidden or invalid |
| `results` | BatchItem[] |  |

### BatchItem object

| Name | Type | Notes |
| --- | --- | --- |
| `id` | string | The id as it was sent |
| `status` | applied \| not_found \| forbidden \| invalid |  |
| `error` | string | Why this item did not apply; absent on applied |
