Search the docs

Find a page, a section, or an endpoint.

Messages

Every messages endpoint, generated from the API's own schemas.

List messages

get/api/v1/messages

Parameters

  • limitnumber
  • cursorstring
  • thread_iduuidrequired
  • bodyenum

    original · clean

Responses

  • 200A page of messages.
  • 400The request is not valid. `error.field` names the input at fault when there is one.
  • 401No API key, or one that is not valid.
  • 403Refused. `error.code` says why: `forbidden` when this key may not do this or the account is on hold, `abuse_blocked` when a limit says no.
  • 404No such resource, or one this key cannot reach. The two answer the same, so treat a 404 as “not available to you” rather than “does not exist”.
  • 409It conflicts with what is there: it already exists, or the `etag` you sent is not the current one.
  • 429Rate limited. `X-RateLimit-Reset` says when to retry.
  • 500Something went wrong on our side. Retry; if it keeps happening, tell us.
  • 503Something this needs is not answering right now. Retry in a moment.
bash
curl https://pidgeon.ai/api/v1/messages \
  -H "Authorization: Bearer $PIDGEON_API_KEY"

Generated · public/openapi.json

Send a message

post/api/v1/messages

Idempotent with an `Idempotency-Key` header. Answers `202` with the id, the thread and anything **rejected** — addresses the provider would not accept at hand-off, so read it before assuming every recipient got it. A recipient this address is not allowed to write to is a different thing and refuses the whole send: `403` with `blocked` when a rule says no, `not_permitted` when the send policy does. Nothing is sent to anybody in that case.

Parameters

  • Idempotency-Keystring

    Any string unique to this attempt, reused on its retries. A repeat with the same key and the same request returns the first answer with `Idempotent-Replay: true` and does nothing — no second email. The same key with a different request is a `409`. Kept for 24 hours; a failure is not kept, so a retry after one runs.

Body

  • fromemailrequired

    An address on a domain you own, and one this key may send from.

  • toemail | email[]required

    One address or several. A string is accepted for the single case.

  • ccemail | email[]

    Copied, and visible to everybody who receives the message.

  • bccemail | email[]

    Copied, and visible to nobody else.

  • subjectstring

    Left off a reply, where the thread already has one.

  • textstring

    The plain-text body. Send this even when you send `html` — some clients only read it.

  • htmlstring

    The rich body. Sanitised on the way in; scripts and remote content are removed.

  • dataobject

    Typed data: structured JSON sent beside the text, for software at the other end to read as fields. Travels as its own part; it is never mixed into the text.

  • attachment_idsuuid[]

    Ids from `POST /attachments`, uploaded before the message that carries them. Up to 20.

  • in_reply_touuid | null

    The message being answered. Keeps the conversation together in the recipient’s client too.

  • in_reply_to_message_iduuid | null

    The same field as `in_reply_to`, under the name this endpoint has always used. Both names work on both endpoints; reach for `in_reply_to`.

Responses

  • 202Accepted.
  • 400The request is not valid. `error.field` names the input at fault when there is one.
  • 401No API key, or one that is not valid.
  • 403Refused, and nothing was sent to anybody. `error.code` says why: `blocked` (a rule), `not_permitted` (the send policy), `abuse_blocked` (a sending limit) or `forbidden` (the address is not one this key can send from, or is not active).
  • 404No such resource, or one this key cannot reach. The two answer the same, so treat a 404 as “not available to you” rather than “does not exist”.
  • 409This `Idempotency-Key` was used with a different request, or that request is still running. Nothing was sent by this call.
  • 429Rate limited. `X-RateLimit-Reset` says when to retry.
  • 500Something went wrong on our side. Retry; if it keeps happening, tell us.
  • 503Something this needs is not answering right now. Retry in a moment.
bash
curl https://pidgeon.ai/api/v1/messages \
  -X POST \
  -H "Authorization: Bearer $PIDGEON_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ … }'

Generated · public/openapi.json

get/api/v1/messages/search

Full-text, ranked by relevance, across every address this credential can reach. Name an `identity_id` to search exactly one. Every result carries the address it was found in and the folder it is in, because a hit you cannot attribute to a mailbox is a hit you cannot open. Not paged: page two of a relevance ranking is a place nobody goes, so narrow the query instead.

Parameters

  • identity_iduuid
  • qstring
  • inenum

    inbox · starred · sent · drafts · scheduled · snoozed · archive · spam · trash · unauthenticated

  • fromstring
  • tostring
  • has_attachmentstring
  • is_unreadstring
  • is_starredstring
  • label_iduuid
  • limitnumber

Responses

  • 200What matched.
  • 400The request is not valid. `error.field` names the input at fault when there is one.
  • 401No API key, or one that is not valid.
  • 403Refused. `error.code` says why: `forbidden` when this key may not do this or the account is on hold, `abuse_blocked` when a limit says no.
  • 404No such resource, or one this key cannot reach. The two answer the same, so treat a 404 as “not available to you” rather than “does not exist”.
  • 409It conflicts with what is there: it already exists, or the `etag` you sent is not the current one.
  • 429Rate limited. `X-RateLimit-Reset` says when to retry.
  • 500Something went wrong on our side. Retry; if it keeps happening, tell us.
  • 503Something this needs is not answering right now. Retry in a moment.
bash
curl https://pidgeon.ai/api/v1/messages/search \
  -H "Authorization: Bearer $PIDGEON_API_KEY"

Generated · public/openapi.json

Wait for the next message

get/api/v1/messages/await

Holds the request open until a message matching the filters arrives at the address, or `timeout` seconds pass — one call where you would otherwise poll. The message carries `extracted`: verification codes and links, most likely first. Without `after`, it first looks back 2 minutes and returns the newest matching message from then, because a code often lands before the call is made; narrow it with `from`, `subject_contains` or `has_code` so an earlier email is not taken for the one you want, or pass `after=now` to wait only for new mail. Pass the returned `cursor` as `after` on the next call so nothing arriving between calls is missed. Answers 200 with `message: null` and `timed_out: true` when nothing came. A matching message filed as spam is not returned unless you pass `include_spam=true`; it is counted in `held_back`, with its id, so a timeout never hides mail that arrived.

Parameters

  • identity_iduuidrequired
  • bodyenum

    original · clean

  • fromstring
  • subject_containsstring
  • has_codeenum

    true · false

  • include_spamenum

    true · false

  • afterstring | date-time
  • timeoutnumber

Responses

  • 200The message, or that none came in time.
  • 400The request is not valid. `error.field` names the input at fault when there is one.
  • 401No API key, or one that is not valid.
  • 403Refused. `error.code` says why: `forbidden` when this key may not do this or the account is on hold, `abuse_blocked` when a limit says no.
  • 404No such resource, or one this key cannot reach. The two answer the same, so treat a 404 as “not available to you” rather than “does not exist”.
  • 409It conflicts with what is there: it already exists, or the `etag` you sent is not the current one.
  • 429Rate limited. `X-RateLimit-Reset` says when to retry.
  • 500Something went wrong on our side. Retry; if it keeps happening, tell us.
  • 503Something this needs is not answering right now. Retry in a moment.
bash
curl https://pidgeon.ai/api/v1/messages/await \
  -H "Authorization: Bearer $PIDGEON_API_KEY"

Generated · public/openapi.json

Fetch one message

get/api/v1/messages/{id}

Parameters

  • idstringrequired
  • bodyenum

    original · clean

Responses

  • 200The message.
  • 400The request is not valid. `error.field` names the input at fault when there is one.
  • 401No API key, or one that is not valid.
  • 403Refused. `error.code` says why: `forbidden` when this key may not do this or the account is on hold, `abuse_blocked` when a limit says no.
  • 404No such resource, or one this key cannot reach. The two answer the same, so treat a 404 as “not available to you” rather than “does not exist”.
  • 409It conflicts with what is there: it already exists, or the `etag` you sent is not the current one.
  • 429Rate limited. `X-RateLimit-Reset` says when to retry.
  • 500Something went wrong on our side. Retry; if it keeps happening, tell us.
  • 503Something this needs is not answering right now. Retry in a moment.
bash
curl https://pidgeon.ai/api/v1/messages/{id} \
  -H "Authorization: Bearer $PIDGEON_API_KEY"

Generated · public/openapi.json

Apply one action to many conversations

post/api/v1/messages/actions

Up to 200, the same cap the dashboard uses. Thread-scoped, like everything else that changes a conversation. The response carries an `undo`.

Body

  • message_idsuuid[]required
  • actionenumrequired

    `read` and `unread` are refused for an API key: read state belongs to a person and a key is not one. Every other action works.

    read · unread · star · unstar · archive · unarchive · delete · restore · spam · not_spam · delete_forever

Responses

  • 200What was applied.
  • 400The request is not valid. `error.field` names the input at fault when there is one.
  • 401No API key, or one that is not valid.
  • 403Refused. `error.code` says why: `forbidden` when this key may not do this or the account is on hold, `abuse_blocked` when a limit says no.
  • 404No such resource, or one this key cannot reach. The two answer the same, so treat a 404 as “not available to you” rather than “does not exist”.
  • 409It conflicts with what is there: it already exists, or the `etag` you sent is not the current one.
  • 429Rate limited. `X-RateLimit-Reset` says when to retry.
  • 500Something went wrong on our side. Retry; if it keeps happening, tell us.
  • 503Something this needs is not answering right now. Retry in a moment.
bash
curl https://pidgeon.ai/api/v1/messages/actions \
  -X POST \
  -H "Authorization: Bearer $PIDGEON_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ … }'

Generated · public/openapi.json

Put an action back

post/api/v1/messages/undo

Hand back the `undo` from a `PATCH` or a batch. Unusual for an API to have, and the argument is that software makes mistakes at a speed a person does not.

Body

  • undoobject[]required

Responses

  • 200How many rows went back.
  • 400The request is not valid. `error.field` names the input at fault when there is one.
  • 401No API key, or one that is not valid.
  • 403Refused. `error.code` says why: `forbidden` when this key may not do this or the account is on hold, `abuse_blocked` when a limit says no.
  • 404No such resource, or one this key cannot reach. The two answer the same, so treat a 404 as “not available to you” rather than “does not exist”.
  • 409It conflicts with what is there: it already exists, or the `etag` you sent is not the current one.
  • 429Rate limited. `X-RateLimit-Reset` says when to retry.
  • 500Something went wrong on our side. Retry; if it keeps happening, tell us.
  • 503Something this needs is not answering right now. Retry in a moment.
bash
curl https://pidgeon.ai/api/v1/messages/undo \
  -X POST \
  -H "Authorization: Bearer $PIDGEON_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ … }'

Generated · public/openapi.json

Change one conversation

patch/api/v1/messages/{id}

Three different kinds of fact: `is_read` is about the reader, `is_starred` is about the mailbox, and `folder` is where the conversation lives. Thread-scoped. The response carries an `undo`.

Parameters

  • idstringrequired

Body

  • is_readboolean

    Refused for an API key: read state belongs to a person and a key is not one. Archive the message, or track what you have seen with `GET /v1/mailboxes/changes`. A person signed in to the dashboard sets this.

  • is_starredboolean
  • folderenum

    inbox · archive · spam · trash

Responses

  • 200What changed.
  • 400The request is not valid. `error.field` names the input at fault when there is one.
  • 401No API key, or one that is not valid.
  • 403Refused. `error.code` says why: `forbidden` when this key may not do this or the account is on hold, `abuse_blocked` when a limit says no.
  • 404No such resource, or one this key cannot reach. The two answer the same, so treat a 404 as “not available to you” rather than “does not exist”.
  • 409It conflicts with what is there: it already exists, or the `etag` you sent is not the current one.
  • 429Rate limited. `X-RateLimit-Reset` says when to retry.
  • 500Something went wrong on our side. Retry; if it keeps happening, tell us.
  • 503Something this needs is not answering right now. Retry in a moment.
bash
curl https://pidgeon.ai/api/v1/messages/{id} \
  -X PATCH \
  -H "Authorization: Bearer $PIDGEON_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ … }'

Generated · public/openapi.json

The message as it arrived

get/api/v1/messages/{id}/raw

`message/rfc822`, not JSON — the point is bytes nobody re-encoded. Received mail is the archived original while it is kept; sent mail, and received mail past its retention, is rebuilt from the stored message. `X-Pidgeon-Reconstructed` says which: a rebuild is not the signed bytes and cannot verify DKIM.

Parameters

  • idstringrequired

Responses

  • 200The message, original or rebuilt (`X-Pidgeon-Reconstructed`).
  • 400The request is not valid. `error.field` names the input at fault when there is one.
  • 401No API key, or one that is not valid.
  • 403Refused. `error.code` says why: `forbidden` when this key may not do this or the account is on hold, `abuse_blocked` when a limit says no.
  • 404No such resource, or one this key cannot reach. The two answer the same, so treat a 404 as “not available to you” rather than “does not exist”.
  • 409It conflicts with what is there: it already exists, or the `etag` you sent is not the current one.
  • 429Rate limited. `X-RateLimit-Reset` says when to retry.
  • 500Something went wrong on our side. Retry; if it keeps happening, tell us.
  • 503Something this needs is not answering right now. Retry in a moment.
bash
curl https://pidgeon.ai/api/v1/messages/{id}/raw \
  -H "Authorization: Bearer $PIDGEON_API_KEY"

Generated · public/openapi.json

Reply to one message

post/api/v1/messages/{id}/reply

The recipients, the subject and the thread are read off the message being answered. You bring the words.

Parameters

  • idstringrequired
  • Idempotency-Keystring

    Any string unique to this attempt, reused on its retries. A repeat with the same key and the same request returns the first answer with `Idempotent-Replay: true` and does nothing — no second email. The same key with a different request is a `409`. Kept for 24 hours; a failure is not kept, so a retry after one runs.

Body

  • textstring

    The plain-text body. Send this even when you send `html` — some clients only read it.

  • htmlstring

    The rich body. Sanitised on the way in; scripts and remote content are removed.

  • dataobject

    Typed data: structured JSON sent beside the text, for software at the other end to read as fields. Travels as its own part; it is never mixed into the text.

  • toemail[]

    One address or several. A string is accepted for the single case.

  • ccemail[]

    Copied, and visible to everybody who receives the message.

  • bccemail[]

    Copied, and visible to nobody else.

  • attachment_idsuuid[]

    Ids from `POST /attachments`, uploaded before the message that carries them. Up to 20.

Responses

  • 202Accepted.
  • 400The request is not valid. `error.field` names the input at fault when there is one.
  • 401No API key, or one that is not valid.
  • 403Refused, and nothing was sent to anybody. `error.code` says why: `blocked` (a rule), `not_permitted` (the send policy), `abuse_blocked` (a sending limit) or `forbidden` (the address is not one this key can send from, or is not active).
  • 404No such resource, or one this key cannot reach. The two answer the same, so treat a 404 as “not available to you” rather than “does not exist”.
  • 409This `Idempotency-Key` was used with a different request, or that request is still running. Nothing was sent by this call.
  • 429Rate limited. `X-RateLimit-Reset` says when to retry.
  • 500Something went wrong on our side. Retry; if it keeps happening, tell us.
  • 503Something this needs is not answering right now. Retry in a moment.
bash
curl https://pidgeon.ai/api/v1/messages/{id}/reply \
  -X POST \
  -H "Authorization: Bearer $PIDGEON_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ … }'

Generated · public/openapi.json

Reply to everybody on one message

post/api/v1/messages/{id}/reply-all

Every address on the message except the ones that reach this mailbox — which a caller cannot work out, because it needs the routing table.

Parameters

  • idstringrequired
  • Idempotency-Keystring

    Any string unique to this attempt, reused on its retries. A repeat with the same key and the same request returns the first answer with `Idempotent-Replay: true` and does nothing — no second email. The same key with a different request is a `409`. Kept for 24 hours; a failure is not kept, so a retry after one runs.

Body

  • textstring

    The plain-text body. Send this even when you send `html` — some clients only read it.

  • htmlstring

    The rich body. Sanitised on the way in; scripts and remote content are removed.

  • dataobject

    Typed data: structured JSON sent beside the text, for software at the other end to read as fields. Travels as its own part; it is never mixed into the text.

  • toemail[]

    One address or several. A string is accepted for the single case.

  • ccemail[]

    Copied, and visible to everybody who receives the message.

  • bccemail[]

    Copied, and visible to nobody else.

  • attachment_idsuuid[]

    Ids from `POST /attachments`, uploaded before the message that carries them. Up to 20.

Responses

  • 202Accepted.
  • 400The request is not valid. `error.field` names the input at fault when there is one.
  • 401No API key, or one that is not valid.
  • 403Refused, and nothing was sent to anybody. `error.code` says why: `blocked` (a rule), `not_permitted` (the send policy), `abuse_blocked` (a sending limit) or `forbidden` (the address is not one this key can send from, or is not active).
  • 404No such resource, or one this key cannot reach. The two answer the same, so treat a 404 as “not available to you” rather than “does not exist”.
  • 409This `Idempotency-Key` was used with a different request, or that request is still running. Nothing was sent by this call.
  • 429Rate limited. `X-RateLimit-Reset` says when to retry.
  • 500Something went wrong on our side. Retry; if it keeps happening, tell us.
  • 503Something this needs is not answering right now. Retry in a moment.
bash
curl https://pidgeon.ai/api/v1/messages/{id}/reply-all \
  -X POST \
  -H "Authorization: Bearer $PIDGEON_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ … }'

Generated · public/openapi.json

Forward one message

post/api/v1/messages/{id}/forward

`to` is required: a forward exists to reach somebody the original did not.

Parameters

  • idstringrequired
  • Idempotency-Keystring

    Any string unique to this attempt, reused on its retries. A repeat with the same key and the same request returns the first answer with `Idempotent-Replay: true` and does nothing — no second email. The same key with a different request is a `409`. Kept for 24 hours; a failure is not kept, so a retry after one runs.

Body

  • textstring

    The plain-text body. Send this even when you send `html` — some clients only read it.

  • htmlstring

    The rich body. Sanitised on the way in; scripts and remote content are removed.

  • dataobject

    Typed data: structured JSON sent beside the text, for software at the other end to read as fields. Travels as its own part; it is never mixed into the text.

  • toemail[]

    One address or several. A string is accepted for the single case.

  • ccemail[]

    Copied, and visible to everybody who receives the message.

  • bccemail[]

    Copied, and visible to nobody else.

  • attachment_idsuuid[]

    Ids from `POST /attachments`, uploaded before the message that carries them. Up to 20.

Responses

  • 202Accepted.
  • 400The request is not valid. `error.field` names the input at fault when there is one.
  • 401No API key, or one that is not valid.
  • 403Refused, and nothing was sent to anybody. `error.code` says why: `blocked` (a rule), `not_permitted` (the send policy), `abuse_blocked` (a sending limit) or `forbidden` (the address is not one this key can send from, or is not active).
  • 404No such resource, or one this key cannot reach. The two answer the same, so treat a 404 as “not available to you” rather than “does not exist”.
  • 409This `Idempotency-Key` was used with a different request, or that request is still running. Nothing was sent by this call.
  • 429Rate limited. `X-RateLimit-Reset` says when to retry.
  • 500Something went wrong on our side. Retry; if it keeps happening, tell us.
  • 503Something this needs is not answering right now. Retry in a moment.
bash
curl https://pidgeon.ai/api/v1/messages/{id}/forward \
  -X POST \
  -H "Authorization: Bearer $PIDGEON_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ … }'

Generated · public/openapi.json

Accept, decline or tentatively accept a meeting

post/api/v1/messages/{id}/invitation/respond

The one call here that changes something in somebody else's calendar: an iCalendar `REPLY` goes to the organiser, which is how Google, Outlook and Calendly learn about an acceptance. The message must be carrying an `invitation`, and one of this mailbox's own addresses must be on it — including an address routed to it, which is the half you cannot work out from the message. Idempotent on the answer: the same answer to the same `sequence` sends once and returns `already_sent`.

Parameters

  • idstringrequired
  • Idempotency-Keystring

    Any string unique to this attempt, reused on its retries. A repeat with the same key and the same request returns the first answer with `Idempotent-Replay: true` and does nothing — no second email. The same key with a different request is a `409`. Kept for 24 hours; a failure is not kept, so a retry after one runs.

Body

  • statusenumrequired

    accepted · declined · tentative

  • commentstring

Responses

  • 200Already answered.
  • 202Sent.
  • 400The request is not valid. `error.field` names the input at fault when there is one.
  • 401No API key, or one that is not valid.
  • 403Refused, and nothing was sent to anybody. `error.code` says why: `blocked` (a rule), `not_permitted` (the send policy), `abuse_blocked` (a sending limit) or `forbidden` (the address is not one this key can send from, or is not active).
  • 404No such resource, or one this key cannot reach. The two answer the same, so treat a 404 as “not available to you” rather than “does not exist”.
  • 409This `Idempotency-Key` was used with a different request, or that request is still running. Nothing was sent by this call.
  • 429Rate limited. `X-RateLimit-Reset` says when to retry.
  • 500Something went wrong on our side. Retry; if it keeps happening, tell us.
  • 503Something this needs is not answering right now. Retry in a moment.
bash
curl https://pidgeon.ai/api/v1/messages/{id}/invitation/respond \
  -X POST \
  -H "Authorization: Bearer $PIDGEON_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ … }'

Generated · public/openapi.json