Update flags, labels and keywords for every message in a folder, by label id
Keyed on the label's numeric id — as GET /labels and POST /labels report it — rather than on its name. The id survives a rename (it is what JMAP Mailbox/set and folder-scoped shares key on), so a client that renames folders should hold it: the by-name route's key changes as the operation succeeds. It also never contains a /, so a folder name that does need not be encoded into a path segment. Otherwise identical to the by-name operation.
This is the spelling a browser client needs: a nested folder IS the label called Work/Projects, and a name carrying a / cannot be a path segment.
bearerAuthAuthorizationBearer <token>Two credential kinds share this scheme; GET /api/v1/auth/whoami answers which one you hold and what it reaches.
oek_… API key (minted by POST /api/v1/api-keys; shown once at mint). role: account reaches the full account surface: the directory (domains, routes, patterns, api-keys, accounts, address lists) plus every mailbox the account owns. role: system additionally reaches the operator surface, which is documented separately.
oemp_… mailbox app password (minted by POST /api/v1/identities/{identityId}/credentials). Reaches ONE mailbox: its messages, labels, threads, search, sync, send, filters and PIM collections. Directory routes refuse it with 403 insufficient_scope.
Refusal vocabulary: a missing, malformed, expired, or revoked credential answers 401 unauthorized with a WWW-Authenticate: Bearer challenge; a VALID credential outside its scope answers 403 (insufficient_scope for an app password on a directory route, system_credentials_required where only the operator may call). The public routes (health, branding, token-in-path capabilities) declare security: [].
mailboxId*stringMailbox ULID identifier
labelId*integerThe label's numeric id, as GET /labels and POST /labels report it
0 < valueapplication/json- body
after?integerContinuation cursor: resume above this UID. Pass the previous call's nextAfter.
int640 <= valueflagsClear?array<>Flags to remove
flagsSet?array<>Flags to set
hasFlags?array<>Narrow the selection to messages with ALL of these flags set
keywordsClear?array<string>Custom keywords to remove
keywordsExact?array<string>Exact replacement list of custom keywords
keywordsSet?array<string>Custom keywords to add
labelsAdd?array<string>Labels to add
labelsExact?array<string>Exact replacement list of labels
labelsRemove?array<string>Labels to remove
limit?integerLower the per-call update budget, for a client that wants finer progress than the server's own bound gives. Never raises it.
1 <= valuenotFlags?array<>Narrow the selection to messages with NONE of these set — ["seen"] for the unread-only gesture a folder's unread badge implies
orphans?stringWhat to do if this write would leave the message with no label. 'expunge' (the default) sends the message to Recently deleted. 'archive' keeps the message live by attaching the Archive system label (attaching Archive needs no right beyond the grant's 't' for a scoped viewer). If the write removes Archive itself and would leave no labels, Archive is kept without error. A SCOPED viewer whose write lands the message in Archive has moved it out of their own scope: the answer is still updated, and the hydrated message carries an EMPTY labels list rather than naming a label the grant never included. labelsAdded omits Archive for the same reason.
"expunge""archive"uidMax?integerInclusive UID ceiling — the selection is every member with uid <= this. Omitted on a FIRST call, the label's current highest UID is used and echoed back. REQUIRED once after is given, so a continuation cannot move its own ceiling and sweep in mail that arrived mid-gesture.
int641 <= valueuidValidity?integerPrecondition: refuse with 409 uid_validity_mismatch unless the label still carries this epoch. A bump means the UIDs this selection was taken over no longer name the same messages.
int64Counts for this call, and a cursor if the range is not drained
application/json- response
archived*integerMessages whose last label this patch removed that fell back to the Archive label instead of being expunged. Unlike expunged, this is a SUBSET of updated, not an alternative to it: an archived message was still updated, so examined equals updated + unchanged + expunged + failed and adding archived to updated double counts. Only present when the patch carried orphans: "archive".
examined*integerMemberships this call looked at, filtered-out ones included
expunged*integerMessages whose last label this patch removed: they moved to the Recently-deleted tier, restorable until purge. Counted here rather than in updated.
failed*integerPer-message refusals — a keyword cap, or a row that left the label between this call's scan and its write. Not an error: the rest of the call applied.
nextAfter*|Continuation cursor: call again with after set to this, the same uidMax, and the same patch. null means the range is drained. Repeating a call is harmless — a patch that changes nothing writes nothing, and a filing patch's earlier pages have already left the range.
int64remaining*integerMemberships left in the range this call did not reach, 0 when it finished. This counts what is UNEXAMINED, not what would match a flag filter — it is the figure a progress bar needs, and it is exact.
uidMax*integerThe resolved ceiling this call worked under. Send it back UNCHANGED on a continuation: it is what keeps the selection the one the user saw, and re-resolving it would let mail that arrived since drift into the gesture.
int64uidValidity*integerUIDVALIDITY epoch of the label the patch ran against
int64unchanged*integerMessages in the selection the patch provably would not have changed, so they were skipped
updated*integerMessages the patch changed
curl -X PATCH 'https://api.open.email/api/v1/mailboxes/string/labels/by-id/1/messages' \
-H 'Content-Type: application/json' \
-d '{}'{
"archived": 0,
"examined": 0,
"expunged": 0,
"failed": 0,
"nextAfter": 0,
"remaining": 0,
"uidMax": 0,
"uidValidity": 0,
"unchanged": 0,
"updated": 0
}Update flags, labels and keywords for every… PATCH
Applies one patch to a UID RANGE of this label — the write twin of GET /labels/{label}/messages, and the way to act on a whole folder without naming its…
List message sync index for a label GET
Returns compact message records (UID, MODSEQ, flags, size, dates) ordered by UID for efficient folder synchronization (up to 10,000 per page).