English
Threads
Routes are relative to <PUBLIC_BASE_URL>; request and response bodies are JSON unless stated otherwise. Authentication, scopes, pagination and the error format are described in the REST API guides.
GET /api/v1/threads
List threads
The inbox, by conversation, cursor-paginated. Two orthogonal axes combine: folder (default inbox; all excludes trash and spam) and filter (unread, flagged, with attachments). q searches the current folder; searchEverywhere widens it to every folder, trash and spam included. An unknown folder key is a 400.
Access — Member session or API key with scope messages:read.
Parameters
| Name | In | Type | Required |
|---|---|---|---|
mailboxId | query | string | no |
cursor | query | string | no |
limit | query | integer | no |
q | query | string | no |
filter | query | "all" | "unread" | "flagged" | "attachments" | no |
folder | query | string | no |
searchEverywhere | query | boolean | no |
Responses
200 — A page of threads.
| Field | Type | Required |
|---|---|---|
threads | object[] | yes |
nextCursor | string | null | yes |
JSON Schema
json
{
"type": "object",
"properties": {
"threads": {
"type": "array",
"items": {
"type": "object",
"properties": {
"id": { "type": "string" },
"mailboxId": { "type": "string" },
"subject": { "anyOf": [ { "type": "string" }, { "type": "null" } ] },
"lastMessageAt": { "type": "string" },
"messageCount": {
"type": "integer",
"exclusiveMinimum": 0,
"maximum": 9007199254740991
},
"unreadCount": { "type": "integer", "minimum": 0, "maximum": 9007199254740991 },
"participants": {
"maxItems": 3,
"type": "array",
"items": {
"type": "object",
"properties": { "name": { "type": "string" }, "email": { "type": "string" } },
"required": [ "email" ],
"additionalProperties": false
}
},
"participantCount": { "type": "integer", "minimum": 0, "maximum": 9007199254740991 },
"snippet": { "anyOf": [ { "type": "string" }, { "type": "null" } ] },
"hasAttachments": { "type": "boolean" },
"flags": {
"type": "object",
"properties": {
"seen": { "type": "boolean" },
"flagged": { "type": "boolean" },
"draft": { "type": "boolean" },
"sent": { "type": "boolean" }
},
"required": [ "seen", "flagged", "draft", "sent" ],
"additionalProperties": false
},
"classification": {
"type": "object",
"propertyNames": { "type": "string" },
"additionalProperties": {}
}
},
"required": [
"id",
"mailboxId",
"subject",
"lastMessageAt",
"messageCount",
"unreadCount",
"participants",
"participantCount",
"snippet",
"hasAttachments",
"flags",
"classification"
],
"additionalProperties": false
}
},
"nextCursor": { "anyOf": [ { "type": "string" }, { "type": "null" } ] }
},
"required": [ "threads", "nextCursor" ],
"additionalProperties": false
}400 — The request does not match its schema.
401 — No valid session or API key (auth.unauthenticated).
403 — Refused: insufficient role (auth.forbidden), missing scope (api_key.scope_missing, details.required names it) or a route closed to API keys (api_key.session_required).
429 — The API key exceeded its rate limit (api_key.rate_limited); Retry-After says when to retry.
Error codes — request.bad_request. The common codes (request.bad_request, auth.unauthenticated, api_key.scope_missing, api_key.rate_limited…) apply to every route; see Errors.
GET /api/v1/threads/{id}
Read a thread
The whole conversation in reading order, with each message’s sanitised body. A thread outside the member’s mailboxes is the same 404 as a missing one.
Access — Member session or API key with scope messages:read.
Parameters
| Name | In | Type | Required |
|---|---|---|---|
id | path | string (uuid) | yes |
Responses
200 — The thread.
| Field | Type | Required |
|---|---|---|
thread | object | yes |
messages | object[] | yes |
JSON Schema
json
{
"type": "object",
"properties": {
"thread": {
"type": "object",
"properties": {
"id": { "type": "string" },
"mailboxId": { "type": "string" },
"subject": { "anyOf": [ { "type": "string" }, { "type": "null" } ] },
"lastMessageAt": { "type": "string" },
"messageCount": {
"type": "integer",
"exclusiveMinimum": 0,
"maximum": 9007199254740991
},
"unreadCount": { "type": "integer", "minimum": 0, "maximum": 9007199254740991 },
"participants": {
"maxItems": 3,
"type": "array",
"items": {
"type": "object",
"properties": { "name": { "type": "string" }, "email": { "type": "string" } },
"required": [ "email" ],
"additionalProperties": false
}
},
"participantCount": { "type": "integer", "minimum": 0, "maximum": 9007199254740991 },
"snippet": { "anyOf": [ { "type": "string" }, { "type": "null" } ] },
"hasAttachments": { "type": "boolean" },
"flags": {
"type": "object",
"properties": {
"seen": { "type": "boolean" },
"flagged": { "type": "boolean" },
"draft": { "type": "boolean" },
"sent": { "type": "boolean" }
},
"required": [ "seen", "flagged", "draft", "sent" ],
"additionalProperties": false
},
"classification": {
"type": "object",
"propertyNames": { "type": "string" },
"additionalProperties": {}
}
},
"required": [
"id",
"mailboxId",
"subject",
"lastMessageAt",
"messageCount",
"unreadCount",
"participants",
"participantCount",
"snippet",
"hasAttachments",
"flags",
"classification"
],
"additionalProperties": false
},
"messages": {
"type": "array",
"items": {
"type": "object",
"properties": {
"id": { "type": "string" },
"mailboxId": { "type": "string" },
"from": {
"anyOf": [
{
"type": "object",
"properties": { "name": { "type": "string" }, "email": { "type": "string" } },
"required": [ "email" ],
"additionalProperties": false
},
{ "type": "null" }
]
},
"subject": { "anyOf": [ { "type": "string" }, { "type": "null" } ] },
"snippet": { "anyOf": [ { "type": "string" }, { "type": "null" } ] },
"receivedAt": { "type": "string" },
"folderLabels": { "type": "array", "items": { "type": "string" } },
"flags": {
"type": "object",
"properties": {
"seen": { "type": "boolean" },
"flagged": { "type": "boolean" },
"draft": { "type": "boolean" },
"sent": { "type": "boolean" }
},
"required": [ "seen", "flagged", "draft", "sent" ],
"additionalProperties": false
},
"signals": {
"type": "object",
"properties": {
"isAutoReply": { "type": "boolean" },
"isNoReply": { "type": "boolean" },
"isMailingList": { "type": "boolean" },
"isFromSelf": { "type": "boolean" }
},
"required": [ "isAutoReply", "isNoReply", "isMailingList", "isFromSelf" ],
"additionalProperties": false
},
"hasAttachments": { "type": "boolean" }
},
"required": [
"id",
"mailboxId",
"from",
"subject",
"snippet",
"receivedAt",
"folderLabels",
"flags",
"signals",
"hasAttachments"
],
"additionalProperties": false
}
}
},
"required": [ "thread", "messages" ],
"additionalProperties": false
}401 — No valid session or API key (auth.unauthenticated).
403 — Refused: insufficient role (auth.forbidden), missing scope (api_key.scope_missing, details.required names it) or a route closed to API keys (api_key.session_required).
429 — The API key exceeded its rate limit (api_key.rate_limited); Retry-After says when to retry.
Error codes — request.not_found. The common codes (request.bad_request, auth.unauthenticated, api_key.scope_missing, api_key.rate_limited…) apply to every route; see Errors.
POST /api/v1/threads/{id}/actions
Act on a whole thread
The messages of the thread are resolved by the instance at the time of the call, so a message that arrived after the screen was drawn is included. One operation per message. A thread of 1000 messages or more is refused rather than truncated.
Access — Member session or API key with scope messages:write.
Parameters
| Name | In | Type | Required |
|---|---|---|---|
id | path | string | yes |
Request body (application/json)
| Field | Type | Required |
|---|---|---|
action | string (enum) | yes |
labels | object | no |
clientToken | string | yes |
JSON Schema
json
{
"type": "object",
"properties": {
"action": {
"type": "string",
"enum": [
"markRead",
"markUnread",
"flag",
"unflag",
"archive",
"unarchive",
"trash",
"restore",
"spam",
"notSpam",
"deletePermanently",
"move"
]
},
"labels": {
"type": "object",
"properties": {
"add": {
"maxItems": 20,
"type": "array",
"items": { "type": "string", "minLength": 1, "maxLength": 200 }
},
"remove": {
"maxItems": 20,
"type": "array",
"items": { "type": "string", "minLength": 1, "maxLength": 200 }
}
}
},
"clientToken": {
"type": "string",
"minLength": 8,
"maxLength": 128,
"pattern": "^[A-Za-z0-9._:-]+$"
}
},
"required": [ "action", "clientToken" ]
}Responses
202 — The queued operations.
| Field | Type | Required |
|---|---|---|
threadId | string | yes |
ops | object[] | yes |
JSON Schema
json
{
"type": "object",
"properties": {
"threadId": { "type": "string" },
"ops": {
"type": "array",
"items": {
"type": "object",
"properties": {
"messageId": { "type": "string" },
"opId": { "type": "string" },
"duplicate": { "type": "boolean" }
},
"required": [ "messageId", "opId", "duplicate" ],
"additionalProperties": false
}
}
},
"required": [ "threadId", "ops" ],
"additionalProperties": false
}400 — The request does not match its schema.
401 — No valid session or API key (auth.unauthenticated).
403 — Refused: insufficient role (auth.forbidden), missing scope (api_key.scope_missing, details.required names it) or a route closed to API keys (api_key.session_required).
429 — The API key exceeded its rate limit (api_key.rate_limited); Retry-After says when to retry.
This operation accepts an Idempotency-Key header: replaying the same request with the same key returns the original response instead of acting twice. See Idempotency.
Error codes — webmail.bad_request, webmail.thread_not_found, webmail.thread_too_large, webmail.message_not_found, webmail.unsupported_action. The common codes (request.bad_request, auth.unauthenticated, api_key.scope_missing, api_key.rate_limited…) apply to every route; see Errors.