# API reference

Transactional email delivery. Every request carries an API key as a bearer token: a server key (sk_) for everything inside one server, an account key (ak_) for what belongs to the account; a key of the wrong kind answers 403 wrong_token_kind. Every error answers a JSON body with an error code and a message.

Base URL `https://api.sendora.se`. Every request carries `Authorization: Bearer` and an API key: a server key (`sk_`) for everything inside one server, an account key (`ak_`) for what belongs to the account. Every error answers JSON with an `error` code and a `message`; the codes are listed under each route. Timestamps are ISO 8601 in UTC. Each route has a page of its own; the same document is served as [OpenAPI 3.1](https://sendora.se/docs/openapi.json).

## Sending

Hand messages over for delivery.

- [Send one message](https://sendora.se/docs/api/post-v1-email.md): POST `/v1/email`, TypeScript `sendora.email.send(message)`, Python `sendora.email.send(**message)`
- [Send up to 100 messages](https://sendora.se/docs/api/post-v1-email-batch.md): POST `/v1/email/batch`, TypeScript `sendora.email.sendBatch(messages)`, Python `sendora.email.send_batch(messages)`

## Messages

The message log and each message’s timeline.

- [Read a message](https://sendora.se/docs/api/get-v1-messages-id.md): GET `/v1/messages/{id}`, TypeScript `sendora.messages.get(messageId)`, Python `sendora.messages.get(message_id)`
- [Search the message log](https://sendora.se/docs/api/post-v1-messages-search.md): POST `/v1/messages/search`, TypeScript `sendora.messages.search(filters)`, Python `sendora.messages.search(**filters)`

## Streams

The streams of the server: transactional, broadcast, and at most one inbound.

- [List the streams](https://sendora.se/docs/api/get-v1-streams.md): GET `/v1/streams`, TypeScript `sendora.streams.list()`, Python `sendora.streams.list()`
- [Create a stream](https://sendora.se/docs/api/post-v1-streams.md): POST `/v1/streams`, TypeScript `sendora.streams.create({ kind, name })`, Python `sendora.streams.create(kind=..., name=...)`
- [Read a stream](https://sendora.se/docs/api/get-v1-streams-id.md): GET `/v1/streams/{id}`, TypeScript `sendora.streams.get(streamId)`, Python `sendora.streams.get(stream_id)`
- [Update a stream](https://sendora.se/docs/api/patch-v1-streams-id.md): PATCH `/v1/streams/{id}`, TypeScript `sendora.streams.update(streamId, { name, contentRetentionDays })`, Python `sendora.streams.update(stream_id, name=..., content_retention_days=...)`
- [Archive a stream](https://sendora.se/docs/api/post-v1-streams-id-archive.md): POST `/v1/streams/{id}/archive`, TypeScript `sendora.streams.archive(streamId)`, Python `sendora.streams.archive(stream_id)`

## Broadcasts

One message to many, sent as a broadcast.

- [List the broadcasts](https://sendora.se/docs/api/get-v1-broadcasts.md): GET `/v1/broadcasts`, TypeScript `sendora.broadcasts.list(query)`, Python `sendora.broadcasts.list(limit=..., after=...)`
- [Send a broadcast](https://sendora.se/docs/api/post-v1-broadcasts.md): POST `/v1/broadcasts`, TypeScript `sendora.broadcasts.send(broadcast)`, Python `sendora.broadcasts.send(**broadcast)`
- [Get a broadcast](https://sendora.se/docs/api/get-v1-broadcasts-id.md): GET `/v1/broadcasts/{id}`, TypeScript `sendora.broadcasts.get(broadcastId)`, Python `sendora.broadcasts.get(broadcast_id)`
- [Cancel a broadcast](https://sendora.se/docs/api/post-v1-broadcasts-id-cancel.md): POST `/v1/broadcasts/{id}/cancel`, TypeScript `sendora.broadcasts.cancel(broadcastId)`, Python `sendora.broadcasts.cancel(broadcast_id)`

## Suppressions

Addresses the server no longer sends to.

- [List the suppressed addresses](https://sendora.se/docs/api/get-v1-suppressions.md): GET `/v1/suppressions`, TypeScript `sendora.suppressions.list(query)`, Python `sendora.suppressions.list(stream_id=..., limit=..., after=...)`
- [Lift a suppression](https://sendora.se/docs/api/post-v1-suppressions-delete.md): POST `/v1/suppressions/delete`, TypeScript `sendora.suppressions.delete({ address })`, Python `sendora.suppressions.delete(address=...)`

## Webhooks

Where events are posted, signed with each of the webhook’s live secrets, and every delivery of them.

- [List the webhooks](https://sendora.se/docs/api/get-v1-webhooks.md): GET `/v1/webhooks`, TypeScript `sendora.webhooks.list()`, Python `sendora.webhooks.list()`
- [Create a webhook](https://sendora.se/docs/api/post-v1-webhooks.md): POST `/v1/webhooks`, TypeScript `sendora.webhooks.create({ url, events })`, Python `sendora.webhooks.create(url=..., events=...)`
- [Read a webhook](https://sendora.se/docs/api/get-v1-webhooks-id.md): GET `/v1/webhooks/{id}`, TypeScript `sendora.webhooks.get(webhookId)`, Python `sendora.webhooks.get(webhook_id)`
- [Remove a webhook](https://sendora.se/docs/api/delete-v1-webhooks-id.md): DELETE `/v1/webhooks/{id}`, TypeScript `sendora.webhooks.delete(webhookId)`, Python `sendora.webhooks.delete(webhook_id)`
- [List the deliveries of a webhook](https://sendora.se/docs/api/get-v1-webhooks-id-deliveries.md): GET `/v1/webhooks/{id}/deliveries`, TypeScript `sendora.webhooks.deliveries(webhookId, query)`, Python `sendora.webhooks.deliveries(webhook_id, status=..., limit=..., after=...)`
- [Replay a delivery](https://sendora.se/docs/api/post-v1-webhooks-id-deliveries-deliveryid-replay.md): POST `/v1/webhooks/{id}/deliveries/{deliveryId}/replay`, TypeScript `sendora.webhooks.replay(webhookId, deliveryId)`, Python `sendora.webhooks.replay(webhook_id, delivery_id)`
- [Create a signing secret](https://sendora.se/docs/api/post-v1-webhooks-id-secrets.md): POST `/v1/webhooks/{id}/secrets`, TypeScript `sendora.webhooks.createSecret(webhookId)`, Python `sendora.webhooks.create_secret(webhook_id)`
- [Delete a signing secret](https://sendora.se/docs/api/delete-v1-webhooks-id-secrets-secretid.md): DELETE `/v1/webhooks/{id}/secrets/{secretId}`, TypeScript `sendora.webhooks.deleteSecret(webhookId, secretId)`, Python `sendora.webhooks.delete_secret(webhook_id, secret_id)`

## Inbound

The mail the server has received: the messages, their raw form and their attachments.

- [Search received messages](https://sendora.se/docs/api/post-v1-inbound-search.md): POST `/v1/inbound/search`, TypeScript `sendora.inbound.search(filters)`, Python `sendora.inbound.search(**filters)`
- [Read a received message](https://sendora.se/docs/api/get-v1-inbound-id.md): GET `/v1/inbound/{id}`, TypeScript `sendora.inbound.get(inboundMessageId)`, Python `sendora.inbound.get(inbound_message_id)`
- [Download the raw message](https://sendora.se/docs/api/get-v1-inbound-id-raw.md): GET `/v1/inbound/{id}/raw`, TypeScript `sendora.inbound.raw(inboundMessageId)`, Python `sendora.inbound.raw(inbound_message_id)`
- [Download an attachment](https://sendora.se/docs/api/get-v1-inbound-id-attachments-attachmentid.md): GET `/v1/inbound/{id}/attachments/{attachmentId}`, TypeScript `sendora.inbound.attachment(inboundMessageId, attachmentId)`, Python `sendora.inbound.attachment(inbound_message_id, attachment_id)`
- [List the inbound domains](https://sendora.se/docs/api/get-v1-inbound-domains.md): GET `/v1/inbound/domains`, TypeScript `sendora.inboundDomains.list()`, Python `sendora.inbound_domains.list()`
- [Claim a domain for an inbound stream](https://sendora.se/docs/api/post-v1-inbound-domains.md): POST `/v1/inbound/domains`, TypeScript `sendora.inboundDomains.create({ streamId, domain })`, Python `sendora.inbound_domains.create(stream_id=..., domain=...)`
- [Read an inbound domain](https://sendora.se/docs/api/get-v1-inbound-domains-id.md): GET `/v1/inbound/domains/{id}`, TypeScript `sendora.inboundDomains.get(inboundDomainId)`, Python `sendora.inbound_domains.get(inbound_domain_id)`
- [Remove an inbound domain](https://sendora.se/docs/api/delete-v1-inbound-domains-id.md): DELETE `/v1/inbound/domains/{id}`, TypeScript `sendora.inboundDomains.delete(inboundDomainId)`, Python `sendora.inbound_domains.delete(inbound_domain_id)`
- [Check the records now](https://sendora.se/docs/api/post-v1-inbound-domains-id-verify.md): POST `/v1/inbound/domains/{id}/verify`, TypeScript `sendora.inboundDomains.verify(inboundDomainId)`, Python `sendora.inbound_domains.verify(inbound_domain_id)`

## Keys

A server's own keys under one of them, up to two live at once; an account key manages them under /v1/servers/{id}/tokens, and the account's own keys are created in the dashboard.

- [List the keys](https://sendora.se/docs/api/get-v1-tokens.md): GET `/v1/tokens`, TypeScript `sendora.tokens.list()`, Python `sendora.tokens.list()`
- [Create a key](https://sendora.se/docs/api/post-v1-tokens.md): POST `/v1/tokens`, TypeScript `sendora.tokens.create({ name })`, Python `sendora.tokens.create(name=...)`
- [Read a key](https://sendora.se/docs/api/get-v1-tokens-id.md): GET `/v1/tokens/{id}`, TypeScript `sendora.tokens.get(tokenId)`, Python `sendora.tokens.get(token_id)`
- [Revoke a key](https://sendora.se/docs/api/delete-v1-tokens-id.md): DELETE `/v1/tokens/{id}`, TypeScript `sendora.tokens.revoke(tokenId)`, Python `sendora.tokens.revoke(token_id)`

## Servers

The account’s servers and each one’s keys, under an account key: a server is created with its first key and holds up to two. GET /v1/server takes a server key and reads that key’s own server.

- [Read this key's server](https://sendora.se/docs/api/get-v1-server.md): GET `/v1/server`, TypeScript `sendora.server.get()`, Python `sendora.server.get()`
- [List the servers](https://sendora.se/docs/api/get-v1-servers.md): GET `/v1/servers`, TypeScript `account.servers.list()`, Python `account.servers.list()`
- [Create a server](https://sendora.se/docs/api/post-v1-servers.md): POST `/v1/servers`, TypeScript `account.servers.create({ name })`, Python `account.servers.create(name=...)`
- [Read a server](https://sendora.se/docs/api/get-v1-servers-id.md): GET `/v1/servers/{id}`, TypeScript `account.servers.get(serverId)`, Python `account.servers.get(server_id)`
- [Rename a server](https://sendora.se/docs/api/patch-v1-servers-id.md): PATCH `/v1/servers/{id}`, TypeScript `account.servers.update(serverId, { name })`, Python `account.servers.update(server_id, name=...)`
- [Delete a server](https://sendora.se/docs/api/delete-v1-servers-id.md): DELETE `/v1/servers/{id}`, TypeScript `account.servers.delete(serverId)`, Python `account.servers.delete(server_id)`
- [List the keys of a server](https://sendora.se/docs/api/get-v1-servers-id-tokens.md): GET `/v1/servers/{id}/tokens`, TypeScript `account.servers.tokens.list(serverId)`, Python `account.servers.tokens.list(server_id)`
- [Create a key for a server](https://sendora.se/docs/api/post-v1-servers-id-tokens.md): POST `/v1/servers/{id}/tokens`, TypeScript `account.servers.tokens.create(serverId, { name })`, Python `account.servers.tokens.create(server_id, name=...)`
- [Read a key of a server](https://sendora.se/docs/api/get-v1-servers-id-tokens-tokenid.md): GET `/v1/servers/{id}/tokens/{tokenId}`, TypeScript `account.servers.tokens.get(serverId, tokenId)`, Python `account.servers.tokens.get(server_id, token_id)`
- [Revoke a key of a server](https://sendora.se/docs/api/delete-v1-servers-id-tokens-tokenid.md): DELETE `/v1/servers/{id}/tokens/{tokenId}`, TypeScript `account.servers.tokens.revoke(serverId, tokenId)`, Python `account.servers.tokens.revoke(server_id, token_id)`

## Domains

Sending domains and their two DNS records, under an account key.

- [List the sending domains](https://sendora.se/docs/api/get-v1-domains.md): GET `/v1/domains`, TypeScript `account.domains.list()`, Python `account.domains.list()`
- [Add a sending domain](https://sendora.se/docs/api/post-v1-domains.md): POST `/v1/domains`, TypeScript `account.domains.create({ domain })`, Python `account.domains.create(domain=...)`
- [Read a sending domain](https://sendora.se/docs/api/get-v1-domains-id.md): GET `/v1/domains/{id}`, TypeScript `account.domains.get(domainId)`, Python `account.domains.get(domain_id)`
- [Remove a sending domain](https://sendora.se/docs/api/delete-v1-domains-id.md): DELETE `/v1/domains/{id}`, TypeScript `account.domains.delete(domainId)`, Python `account.domains.delete(domain_id)`
- [Check the records now](https://sendora.se/docs/api/post-v1-domains-id-verify.md): POST `/v1/domains/{id}/verify`, TypeScript `account.domains.verify(domainId)`, Python `account.domains.verify(domain_id)`

## Erasures

A person's data erased from every server of the account, under an account key.

- [Erase a person](https://sendora.se/docs/api/post-v1-erasures.md): POST `/v1/erasures`, TypeScript `account.erasures.create({ address })`, Python `account.erasures.create(address=...)`
- [Read an erasure](https://sendora.se/docs/api/get-v1-erasures-id.md): GET `/v1/erasures/{id}`, TypeScript `account.erasures.get(erasureId)`, Python `account.erasures.get(erasure_id)`

## Webhook events

What a webhook receives for each event, with its fields and an example, is on [Webhook events](https://sendora.se/docs/api/webhook-events.md).
