Change flags and labels
Marks messages read, starred or answered, and adds or removes labels. Takes a batch, so one call can change hundreds of messages.
Permission
The key must be granted mail.messages.write
A key without it gets 403 insufficient_scope, with the missing permission named in details.
Takes a batch, so marking five hundred messages read is one call. Reach for set only when you mean "this is the exact set": it removes every flag and label you did not list.
Path parameters
| Field | Type | Description |
|---|---|---|
folder_id required |
string | The folder holding the messages. Flags are set within a folder. |
Request body
| Field | Type | Description |
|---|---|---|
message_ids required |
string[] | The messages to change. All must be in the folder above. |
op required |
string |
add and remove adjust what is there; set replaces it and drops anything not listed.
addremoveset
|
flags |
string[] |
System flags to change.
readstarredanswereddraftdeleted
|
labels |
string[] | User-defined labels to change. Any text without a leading backslash. |
Request
curl https://api.veruapis.com/v1/folders/:folder_id/messages/flags \
-X POST \
-H "Authorization: Bearer vak_live_..." \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{
"message_ids": ["d9038c5a-3bca-4e27-a016-4b7ad1416948"],
"op": "add",
"flags": ["read"],
"labels": ["Invoices"]
}'
Idempotency-Key is required on every write. Retrying with the same key returns the original response rather than repeating the operation, so a timeout is something you retry rather than something you investigate.
Response
| Field | Type | Description |
|---|---|---|
id |
string | |
flags |
object | The flags after the change, not the ones you sent. Its fields are below. |
labels |
string[] |
The flags
| Field | Type | Description |
|---|---|---|
read |
boolean | The IMAP Seen flag. Absent means unread. |
starred |
boolean | The IMAP Flagged bit. One star, not a colour. |
answered |
boolean | Set automatically when you reply through this API. |
draft |
boolean | |
deleted |
boolean | Marked for removal but still present until the folder is expunged. |
{
"data": [
{
"id": "d9038c5a-3bca-4e27-a016-4b7ad1416948",
"flags": { "read": true, "starred": false, "answered": false, "draft": false, "deleted": false },
"labels": ["Invoices"]
}
],
"meta": { "request_id": "req_01j8z9...", "timestamp": "2026-09-02T10:00:00Z" }
}
Takes a batch, so marking five hundred messages read is one call rather than five hundred. op is add, remove or set — reach for set only when you mean "this is the exact label set", because it drops every label you did not list.
Every failure uses the same shape, with a stable string code you can branch on. See the error reference.
Related
- POST Create a draft
- POST Create a mail filter
- POST Create a mail folder
- DELETE Delete a draft
- DELETE Delete a mail filter
- DELETE Delete a mail folder
- DELETE Delete a message
- GET Download an attachment
- GET Get a message
- GET Get display preferences
- GET List attachments
- GET List drafts
- GET List mail filters
- GET List mail folders
- GET List messages
- POST Move messages
- PUT Replace a draft
- POST Send an email
- PATCH Rename a mail folder
- PATCH Update display preferences