API

REST API overview

Authentication, conventions, errors, pagination and realtime endpoints.

Base URL: https://YOUR-INSTANCE/api/v1. The full, generated reference is on the endpoint reference page; the OpenAPI 3.1 document is served at /api/openapi.json by every instance.

Authentication

curl https://app.squadmail.dev/api/v1/me -H "Authorization: Bearer sqm_YOUR_KEY"

Keys are scoped to one workspace. Dashboard sessions also work for same-origin requests.

Conventions

  • JSON in, JSON out. Field names are snake_case, timestamps ISO-8601 UTC.
  • Inbox path parameters accept the id or the address: /inboxes/swift-otter-4821@example.com.
  • Lists return data and next_cursor. Pass cursor to fetch the next page.
  • DELETE returns deleted: true with the id.

Errors

{
	"error": {
		"code": "validation_error",
		"message": "Invalid request",
		"issues": [{ "path": "ttl_minutes", "message": "Too big" }]
	}
}
StatusCodes
400validation_error, invalid_json, invalid_domain, unknown_label
401unauthorized
403forbidden, insufficient_scope, feature_disabled
404not_found
409address_taken, label_exists, no_domain
429rate_limited, limit_reached

Waiting for mail

GET /inboxes/{id}/wait?timeout=60&subject=verify&from=acme blocks until a matching email arrives (max 120 s) and returns it, or 204 on timeout. It is backed by a Durable Object, so there is no polling on your side or ours.

Streaming

GET /inboxes/{id}/stream is a Server-Sent Events stream:

event: email.received
data: {"type":"email.received","inboxId":"inb_…","emailId":"eml_…","from":"no-reply@acme.io","subject":"Your code","code":"482913","at":1791386064000}

Extracted data

Every email has an extracted object computed on ingest — no AI involved:

{
	"code": "482913",
	"link": "https://acme.io/verify?token=abc",
	"codes": ["482913"],
	"links": [
		{ "url": "https://acme.io/verify?token=abc", "text": "Confirm email", "kind": "verify" }
	]
}

Link kinds: verify, login, reset, unsubscribe, other.

Edit this page on GitHub