Every API here is available over the APIs.io API and to AI agents over MCP.
openapi: 3.2.0
info:
title: Dead Simple Email Threads API
version: 0.1.0
description: 'Email API for AI agents. Create inboxes, send and receive email,
manage threads, and get real-time webhook notifications.
## Authentication
All authenticated endpoints use Bearer token auth:
```
Authorization: Bearer dse_your_api_key
```
## Agent self-signup
Autonomous agents can provision an account, a key, and a live inbox in one
unauthenticated call — no human, no verification email:
```
POST /v1/auth/agent-signup
```
That returns a `trial` account (1 inbox, 10 sends/hour, 25 sends/day) with a
scoped key. `POST /v1/auth/claim` attaches a verified email and moves it to
the Free plan, keeping the same key, inboxes and history.
## Sign in with Dead Simple (OpenID Connect)
`https://id.deadsimple.email` is an OpenID Connect provider where the
identity is an agent''s inbox: `sub` is the `inbox_id`, `email` is the inbox
address. Discovery at `/.well-known/openid-configuration`, JWKS at
`/.well-known/jwks.json`, ES256 id_tokens, authorization-code + PKCE S256,
RFC 7591 registration at `/register`. Open clients (client_id = the app''s
https origin) need no registration. An agent authenticates at `/authorize`
with `Authorization: Bearer dse_...` plus `inbox_id=`, or with an ES256
assertion signed by a key enrolled via `/v1/inboxes/{inbox_id}/identity/keys`;
the one-call helper `POST /v1/inboxes/{inbox_id}/identity/sign-in` wraps
this for SDKs and the MCP server. Endpoints on the `id.deadsimple.email`
host are tagged **Identity Provider** below and carry their own `servers`
entry.
## Pay per request (no API key)
Twelve priced routes can be called with no credentials at all. They answer
`402 Payment Required` carrying an x402 v2 `PAYMENT-REQUIRED` header (USDC
on Base, Polygon or Solana, `exact` scheme, PayAI facilitator; the same
JSON is repeated in the body for v1 clients) and, on `POST /v1/inboxes`,
a `WWW-Authenticate: Payment` challenge for Stripe''s Machine Payments
Protocol. Retry with `PAYMENT-SIGNATURE` (x402 v2), `X-PAYMENT` (v1) or
`Authorization: Payment ` (MPP). x402 payments are verified
before the work and settled after it; MPP charges first and refunds if
the work fails. Prices: inbox $1.00, send/reply/reply-all/forward $0.01,
reads $0.005. The paying wallet keys a `payg` account, and the first paid
inbox returns `credentials` (an API key) in the response so later reads
and sends within the plan are free. See the reusable `402` response and
https://deadsimple.email/pay-per-request.html.
## Idempotency
POST endpoints accept an `Idempotency-Key` header. Same key within 24h
returns the cached response without re-executing.
## Rate Limits
Every response includes rate limit headers:
- `X-RateLimit-Limit` — requests per window
- `X-RateLimit-Remaining` — remaining requests
- `X-RateLimit-Reset` — window reset timestamp
On `429`, a `Retry-After` header gives the seconds to wait. Per-account
limits scale with plan (`rate_limit_rpm`); public/unauthenticated endpoints
are additionally limited per IP.
## Webhook Reliability
Every event is a durable delivery: the first attempt runs immediately and
failures are retried on an exponential-backoff schedule (configurable per
webhook via `/v1/webhooks/{id}/config`; default `[1, 30, 300, 1800, 7200,
86400]` seconds). Deliveries are HMAC-SHA256 signed on **all plans**. The
per-webhook delivery log (`/v1/webhooks/{id}/deliveries`) records every
attempt with status and error, and any delivery can be replayed. Endpoints
that fail repeatedly are auto-disabled.
## WebSocket Streaming
Connect to `wss://ws.deadsimple.email?api_key=dse_...` for real-time events.
After connecting, subscribe to specific inboxes/events:
```json
{"action": "subscribe", "inbox_ids": ["..."], "events": ["message.received"]}
```
Events are pushed as JSON with the same payload as webhooks. Send `{"action": "ping"}`
to keep the connection alive (10-minute idle timeout).'
contact:
url: https://deadsimple.email
license:
name: MIT
servers:
- url: https://api.deadsimple.email
description: Production
security:
- BearerAuth: []
tags:
- name: Threads
paths:
/v1/threads:
get:
summary: List threads across all inboxes (org-wide)
description: Returns threads from every inbox in the account. Useful for supervisor agents monitoring multiple agent conversations.
tags:
- Threads
parameters:
- $ref: '#/components/parameters/WorkspaceHeader'
- name: inbox_id
in: query
required: false
schema:
type: string
format: uuid
description: Filter to threads from a specific inbox
- name: labels
in: query
schema:
type: string
description: Comma-separated labels to filter by
- name: limit
in: query
schema:
type: integer
default: 50
- name: offset
in: query
schema:
type: integer
default: 0
responses:
'200':
description: Thread summaries across all inboxes
operationId: getV1Threads
x-operation-id-source: derived
/v1/inboxes/{inbox_id}/threads:
get:
summary: List threads in an inbox
tags:
- Threads
parameters:
- name: inbox_id
in: path
required: true
schema:
type: string
- $ref: '#/components/parameters/WorkspaceHeader'
- name: limit
in: query
schema:
type: integer
default: 50
responses:
'402':
$ref: '#/components/responses/PaymentRequired'
'200':
description: Thread summaries with latest message
operationId: getV1InboxesByInboxIdThreads
x-operation-id-source: derived
/v1/inboxes/{inbox_id}/threads/{thread_id}:
get:
summary: Get thread with all messages
tags:
- Threads
parameters:
- name: inbox_id
in: path
required: true
schema:
type: string
- name: thread_id
in: path
required: true
schema:
type: string
- $ref: '#/components/parameters/WorkspaceHeader'
responses:
'402':
$ref: '#/components/responses/PaymentRequired'
'200':
description: All messages in the thread, chronological
operationId: getV1InboxesByInboxIdThreadsByThreadId
x-operation-id-source: derived
delete:
summary: Delete a thread
description: Deletes every message of the thread in this inbox, with the same behaviour as deleting each message. Needs `threads.delete`.
tags:
- Threads
parameters:
- name: inbox_id
in: path
required: true
schema:
type: string
- name: thread_id
in: path
required: true
schema:
type: string
- $ref: '#/components/parameters/WorkspaceHeader'
responses:
'200':
description: Deleted
content:
application/json:
example:
thread_id: 9a1e5c3b-7d2f-4b8a-a6e0-3c9f1d7b5e24
deleted: true
messages_deleted: 4
'404':
description: No such thread in this inbox
operationId: deleteV1InboxesByInboxIdThreadsByThreadId
x-operation-id-source: derived
/v1/inboxes/{inbox_id}/threads/{thread_id}/attachments:
get:
summary: Get all attachments in a thread
tags:
- Threads
parameters:
- name: inbox_id
in: path
required: true
schema:
type: string
- name: thread_id
in: path
required: true
schema:
type: string
- $ref: '#/components/parameters/WorkspaceHeader'
responses:
'200':
description: All attachments across every message in the thread, with message_id attribution
operationId: getV1InboxesByInboxIdThreadsByThreadIdAttachments
x-operation-id-source: derived
components:
schemas:
PaymentRequiredBody:
type: object
description: Our standard error envelope plus the x402 v2 fields (minus its own error string) so that x402 v1 clients, which read requirements from the body, can pay too.
properties:
error:
type: object
properties:
code:
type: string
example: payment_required
message:
type: string
price_usd:
type: string
example: '1.00'
docs:
type: string
example: https://deadsimple.email/pay-per-request.html
reason:
type: string
description: Present when a submitted payment was rejected (amount mismatch, network not accepted, facilitator invalidReason, declined card, challenge reused).
meta:
type: object
properties:
request_id:
type: string
x402Version:
type: integer
example: 2
resource:
type: object
properties:
url:
type: string
description:
type: string
mimeType:
type: string
accepts:
type: array
items:
$ref: '#/components/schemas/X402Requirement'
extensions:
type: object
X402Requirement:
type: object
description: One accepted way to pay, from the x402 v2 accepts array.
properties:
scheme:
type: string
example: exact
network:
type: string
description: CAIP-2 id. eip155:8453 (Base), eip155:137 (Polygon) or solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp.
amount:
type: string
description: Atomic USDC, six decimals. "1000000" is $1.00.
asset:
type: string
description: USDC contract (EVM) or mint (Solana).
payTo:
type: string
maxTimeoutSeconds:
type: integer
example: 300
extra:
type: object
description: EIP-712 domain {name, version} on EVM networks; {feePayer} on Solana.
responses:
PaymentRequired:
description: Payment required. The request had no API key and this route can be paid per call. The x402 requirements are in the PAYMENT-REQUIRED header and repeated in the body; routes priced at or above $0.50 (inbox creation) also carry a Stripe MPP challenge in WWW-Authenticate. A rejected or mismatched payment gets this same response again with error.reason explaining why. Responses are Cache-Control no-store and expose the payment headers to browsers.
headers:
PAYMENT-REQUIRED:
description: 'Base64 JSON, x402 v2 PaymentRequired object: x402Version, resource, accepts (one entry per network with scheme, network, amount in atomic USDC, asset, payTo, maxTimeoutSeconds, extra), extensions.'
schema:
type: string
WWW-Authenticate:
description: 'Stripe MPP challenge, only when the price is at least $0.50: `Payment id="...", realm="api.deadsimple.email", method="stripe", intent="charge", expires="<ISO 8601>", request="<base64url JCS JSON {amount (cents), currency, description, methodDetails}>"`. Valid five minutes, single use.'
schema:
type: string
Cache-Control:
schema:
type: string
example: no-store
content:
application/json:
schema:
$ref: '#/components/schemas/PaymentRequiredBody'
parameters:
WorkspaceHeader:
name: X-Workspace-Id
in: header
required: false
schema:
type: string
format: uuid
description: Target a specific workspace. Required for account-wide API keys that want workspace-scoped results.
securitySchemes:
BearerAuth:
type: http
scheme: bearer
description: API key (dse_...), session token (JWT), or OAuth 2.1 access token (dse_oat_..., obtained via the authorization-code + PKCE flow below)
OAuth2:
type: oauth2
description: OAuth 2.1 for MCP clients. Public clients only (PKCE S256 required, no client secret). Supports RFC 7591 dynamic client registration at /oauth/register and discovery via /.well-known/oauth-authorization-server. Access tokens carry inbox/message permissions only (no billing, team, or API-key administration).
flows:
authorizationCode:
authorizationUrl: https://api.deadsimple.email/oauth/authorize
tokenUrl: https://api.deadsimple.email/oauth/token
refreshUrl: https://api.deadsimple.email/oauth/token
scopes:
email: Send, receive, and manage email inboxes
OpenIDConnect:
type: openIdConnect
openIdConnectUrl: https://id.deadsimple.email/.well-known/openid-configuration
description: 'Sign in with Dead Simple. Relying parties use this to authenticate an agent by its inbox; it is not used to call the Dead Simple API itself. Scopes: openid, email, profile (open clients); owner_email, org, offline_access (registered clients only).'