Skip to content

HTTP API

Every endpoint below is derived from the running application's OpenAPI schema, so it cannot describe an endpoint that does not exist or miss a field that does. The raw schema is openapi.json; a running instance also serves it at /openapi.json, with interactive documentation at /docs.

Authentication

Every /v1 endpoint takes an API key as a bearer token:

curl https://your-seskit-instance/v1/emails/email_01J8XQ... \
  -H "Authorization: Bearer sk_..."

Keys are scoped to a project and created in the dashboard. A missing header and an invalid token both return 401 with the same message — distinguishing them would tell whoever is probing which half they had right.

Errors

One shape, always:

{ "error": { "type": "domain_not_verified", "message": "..." } }

Branch on type, which is stable; show message to humans. The full list is in Errors.

Rate limits

Per project, not per key. Every response carries X-RateLimit-Limit, X-RateLimit-Remaining and X-RateLimit-Reset; exceeding the limit returns 429.

One endpoint is deliberately absent

POST /v1/events/ses receives SES notifications from Amazon SNS and is excluded from the schema, because it is not for callers — it authenticates by verifying SNS's signature rather than by API key, and is only mounted when EVENT_INGESTION asks for it. See delivery events.

Endpoints

Method Path
GET /v1/api-keys List API keys
GET /v1/domains List sending domains
POST /v1/emails Send an email
GET /v1/emails List emails
GET /v1/emails/{email_id} Retrieve an email
GET /v1/webhooks List webhook endpoints
GET /v1/webhooks/{endpoint_id}/deliveries List recent webhook deliveries

GET /v1/api-keys

List API keys

Every key belonging to the calling key's project.

Scoped by the authenticated project rather than by anything in the request, so there is no parameter a caller could change to see someone else's keys.

Responses

Status
200 Successful Response
401 Invalid or missing API key.
429 Rate limit exceeded.

GET /v1/domains

List sending domains

Every domain this key's project can send from.

Responses

Status
200 Successful Response
401 Invalid or missing API key.
429 Rate limit exceeded.

POST /v1/emails

Send an email

Accept a message for sending.

Parameters

Name In
Idempotency-Key header optional Repeat a request safely. A second send with the same key returns the first message's id and sends nothing further, so a retry after a timeout cannot deliver twice. Scoped to the project.

Request body

Field Type
from string required The sender, optionally with a display name. The address or its domain must be verified in SES — an unverified sender is refused by SES, not by SESKit, so it fails at send time rather than here.
to string[] or string required One recipient, or a list of them. While your account is in the SES sandbox every recipient must also be verified.
subject string required Max 998 characters, which is the RFC 5322 line limit.
html string optional HTML body. Provide html, text, or both; a message with neither is refused.
text string optional Plain-text body. Sending both makes a multipart message, which is what clients that will not render HTML fall back to.
cc string[] or string optional Visible to every recipient.
bcc string[] or string optional Hidden from every recipient. Recorded, but never returned by the API — a blind copy readable from a GET is not blind.
reply_to string[] or string optional Where replies go, if not to from. Needs no SES verification.
headers object optional Custom headers to add to the message. Names are RFC 5322 field-names: printable ASCII with no colon, spaces or line breaks.
attachments AttachmentRequest[] optional The size limit applies to the assembled message, not to each file: base64 inflates content by about a third, and it is the assembled size SES rejects. See EMAIL_MAX_MESSAGE_BYTES (10 MiB by default).

Responses

Status
201 Successful Response
401 Invalid or missing API key.
422 Validation Error
429 Rate limit exceeded.

GET /v1/emails

List emails

This key's project, newest first.

Paged by cursor rather than by offset. Ids sort in the order they were created, so starting_after names a fixed point in the list and stays correct while new messages arrive underneath it. An offset does not: a send between two pages shifts every row down one, and the reader silently skips the message that moved across the boundary. For a send log that is data loss nobody can see.

An unknown starting_after is a 404 rather than an empty page. The comparison is lexical, so an id from another project would otherwise return a page of real messages positioned by an id the caller cannot see - a wrong answer that looks like a right one.

Parameters

Name In
limit query optional How many messages to return, newest first.
starting_after query optional Return messages older than this id - the last id from the previous page. The id must belong to this project.
status query optional Only messages in this status.

Responses

Status
200 Successful Response
401 Invalid or missing API key.
422 Validation Error
429 Rate limit exceeded.

GET /v1/emails/{email_id}

Retrieve an email

One message, if it belongs to this key's project.

Ownership is part of the query, so an id from another project is a 404 rather than a 403 - which would confirm the id exists.

Parameters

Name In
email_id path required The id returned when the message was accepted.

Responses

Status
200 Successful Response
401 Invalid or missing API key.
422 Validation Error
429 Rate limit exceeded.

GET /v1/webhooks

List webhook endpoints

Every webhook endpoint registered on this key's project.

The signing secret is not included - see the note in schemas/webhooks.py. It is available on the dashboard, where it is read once by a person rather than returned to code on every call.

Responses

Status
200 Successful Response
401 Invalid or missing API key.
429 Rate limit exceeded.

GET /v1/webhooks/{endpoint_id}/deliveries

List recent webhook deliveries

Recent delivery attempts for one endpoint, newest first.

The delivery row is the queue as well as the log, so what comes back is the actual state of each attempt - including one still pending a retry, with the time it is next due.

Parameters

Name In
endpoint_id path required The endpoint whose deliveries to list.

Responses

Status
200 Successful Response
401 Invalid or missing API key.
422 Validation Error
429 Rate limit exceeded.

Models

AttachmentRequest

One attachment, base64 encoded.

JSON has no byte type, so content arrives encoded. The size limit is applied to the assembled message rather than to this field, because base64 inflates content by about a third and it is the assembled size SES rejects.

Field Type
filename string required The name the recipient sees. Max 255 characters.
content string required Base64-encoded file content.
content_type string optional MIME type. Defaults to application/octet-stream, which most clients offer as a download rather than displaying.