Search the docs

Find a page, a section, or an endpoint.

Reading and triaging

Folders, paging, changing a conversation, undo and search.

Every address with an inbox has one, and the API reads and writes it: folders with counts, a page of conversations, and the state changes a person would make by clicking. The mailbox is not a browser-only half of the product.

The folders

Nine, and they are the same nine the web client has. identity_id is required — there is no merged view across addresses, because two mailboxes joined together are a mailbox nobody has.

curl "https://pidgeon.ai/api/v1/mailboxes?identity_id=idn_4f2a1" \
-H "Authorization: Bearer $PIDGEON_API_KEY"

total and unread are null rather than 0 where the product does not count that folder. Zero would be a claim; null says we do not count it.

One folder, a page at a time

A row is a conversation — the newest message in each thread — not a message. It carries no body: a list of forty conversations does not want forty bodies. GET /messages?thread_id= opens one.

curl "https://pidgeon.ai/api/v1/mailboxes/inbox?identity_id=idn_4f2a1&limit=50" \
-H "Authorization: Bearer $PIDGEON_API_KEY"

cursor is opaque, and it is not an offset. It carries a sort key and an id together, because several messages can arrive in the same millisecond and a cursor on the timestamp alone repeats or drops rows at a page boundary. An offset would be worse than either: rows shift underneath it and a client syncing an inbox silently loses mail. Pass back what next_cursor gave you; do not construct one.

A cursor we did not write is page one, not an error. It arrives in a URL, and a URL is a thing people edit.

Changing a conversation

Three fields, and they are three different kinds of fact: is_read is about the reader, is_starred is about the mailbox, and folder is where the conversation lives.

curl https://pidgeon.ai/api/v1/messages/msg_9a13c \
-X PATCH \
-H "Authorization: Bearer $PIDGEON_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "is_read": true, "folder": "archive" }'

Everything here is thread-scoped: archiving one message of a conversation and leaving the rest in the inbox is never what anyone means. The four folders you can move a conversation to are inbox, archive, spam and trash — the ones that are places. Starred is a view over a flag, which is why it is is_starred and not a destination.

POST /messages/actions applies one action to up to 200 conversations, which is the same cap the dashboard uses.

Undo

Unusual for an API to have, and the argument is that software makes mistakes at a speed a person does not. An agent that archived the wrong forty conversations should be able to say so.

curl https://pidgeon.ai/api/v1/messages/undo \
-X POST \
-H "Authorization: Bearer $PIDGEON_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "undo": [ ... ] }'

The undo value is the rows as they were before. Hand it back; do not read it. Access to the messages it names is re-checked, so the worst a tampered one can do is set flags on mail you can already set flags on.

Full-text over one mailbox, ranked by relevance. A result carries the folder it was found in — the whole reason you are searching is that you do not know where it lives.

curl "https://pidgeon.ai/api/v1/messages/search?identity_id=idn_4f2a1&q=invoice" \
-H "Authorization: Bearer $PIDGEON_API_KEY"

Not paged: page two of a relevance ranking is a place nobody goes. Narrow the query instead.