llms.txt
# EmailFlow AI
> AI-native email marketing platform: audiences (lists, subscribers, segments),
> campaigns, automations, trigger events, transactional email, AI email
> generation, sending domains, webhooks, and analytics, all available over a
> scoped REST API.
Authoritative resources:
- OpenAPI 3.1 spec (machine-readable contract): https://emailflow.ai/openapi/public-api-v1.json
(YAML: https://emailflow.ai/openapi/public-api-v1.yaml)
- Human docs: https://emailflow.ai/docs (developer section: https://emailflow.ai/docs/api)
- Full docs corpus for agents: https://emailflow.ai/llms-full.txt
- TypeScript SDK: @emailflow/sdk (sdk/typescript in the repository)
- API base URL: https://emailflow.ai/api/v1
## Authentication
Create scoped API keys under Account -> API (https://emailflow.ai/rui/account/api). Keys
look like `efa_` + 40 base62 characters and are shown ONCE at creation.
Only a sha256 hash is stored. Send the key on every request using either
header form:
Authorization: Bearer efa_your_key_here
X-API-Key: efa_your_key_here
Both forms are equivalent. Keys can expire and be revoked; a revoked or
expired key returns 401 `INVALID_API_KEY`.
## Scopes
Grant only what an integration needs. Broad scopes include their
narrower scopes (the Includes column); granular scopes grant only
themselves. A key missing the required scope gets 403
`INSUFFICIENT_PERMISSIONS`.
Scope Grants Includes
----------- ---------------------------------------------------- -------------------------
all Everything, including admin endpoints every scope
contacts Lists and subscribers; includes Audiences audiences
audiences Read audience segments -
emails Email content; includes Domains, Sends and Templates domains, sends, templates
domains Sending domains -
sends Campaigns and sending -
templates Email templates -
automations Automation workflows; includes Triggers triggers
triggers Trigger automation runs -
webhooks Webhook subscriptions -
analytics Event analytics feed -
usage Usage and quota meters -
brand Brand profile -
## Idempotency
Unsafe requests (POST/PATCH/PUT/DELETE) accept an optional `Idempotency-Key`
header (any string up to 255 chars; UUIDs recommended). Within a 24-hour
window, replaying the same key + identical payload returns the ORIGINAL
response with `Idempotency-Replayed: true` instead of re-executing. For example, a
replayed `POST /emails/generate` returns the same `template_uid` without
generating (or charging) twice. The same key with a DIFFERENT payload
returns 409 `CONFLICT`, as does a concurrent in-flight request with the
same key. 5xx responses are not recorded, so retries after a server error
re-execute.
## Rate limits
Per API key (legacy-token callers share a per-user bucket). Successful
responses carry `X-RateLimit-Limit`, `X-RateLimit-Remaining`, and
`X-RateLimit-Reset`; a 429 carries `Retry-After` (seconds). Honor it
and retry with backoff.
Class Limit Applies to
--------- ------- -------------------------------------------------------------
api.read 100/min All read (GET) requests on resource endpoints.
api.write 60/min Create, update, and delete requests on resource endpoints.
api.batch 10/min Bulk subscriber import (sync and async) and batch operations.
api.ai 20/min AI email generation and edit requests.
api.sends 10/min Campaign run (send) triggers and transactional sends.
Untagged legacy endpoints share an outer 60/min guard.
## Errors
Every error is a uniform JSON envelope:
{"error": {"code", "type", "message", "suggestion"?, "docs"?,
"param"?, "details"?, "retryAfter"?}}
Cross-tenant and unknown uids both return 404 `NOT_FOUND`. Existence
is never leaked.
HTTP Code Type What to do
---- ------------------------ --------------------- ---------------------------------------------------------------------------------------------------
400 INVALID_REQUEST invalid_request_error Check the request method, path, and parameters against the API reference.
401 INVALID_API_KEY authentication_error Create a new key under Account -> API, or check that the key was copied in full.
403 INSUFFICIENT_PERMISSIONS permission_error Use a key whose scopes cover this endpoint, or create one under Account -> API.
404 NOT_FOUND invalid_request_error Check the identifier; the resource may have been deleted or may belong to another account.
422 VALIDATION_ERROR invalid_request_error Fix the fields listed in details and retry the request.
429 RATE_LIMITED rate_limit_error Slow down and retry after the number of seconds given in retryAfter.
409 IDEMPOTENCY_CONFLICT invalid_request_error Use a fresh Idempotency-Key for a different request body, or replay the identical body.
402 CREDITS_EXHAUSTED billing_error Upgrade your plan or wait for the next billing cycle to refresh your credits.
402 PLAN_LIMIT billing_error Upgrade your plan to raise this limit.
409 CONFLICT invalid_request_error The resource is in a state that does not allow this operation; fetch it again and check its status.
500 SERVER_ERROR api_error Retry later; if the problem persists, contact support with the request timestamp.
## Resources
Base URL: https://emailflow.ai/api/v1. Full request/response shapes live in the OpenAPI spec.
### Lists (`contacts`)
CRUD at `/lists`; add custom fields with `POST /lists/{uid}/add-field`. Lists are the container every subscriber, segment, and automation hangs off.
### Subscribers (`contacts`)
CRUD at `/subscribers` (+ `?list_uid=` filter); per-list status transitions under `/lists/{list_uid}/subscribers/...`; tag with `add-tag`/`remove-tag`. Custom-field values travel as UPPERCASE tag keys (e.g. `FIRST_NAME`).
### Bulk contacts (`contacts`)
Up to 1,000 rows per call: `POST /lists/{uid}/subscribers/bulk` (sync upsert), `DELETE .../bulk` (bulk delete), `POST .../bulk/async` (import job; poll `GET /import-jobs/{uid}`).
### Segments (`contacts`)
Condition-based audiences: `/lists/{uid}/segments` + `/segments/{uid}`; preview membership with `GET /segments/{uid}/subscribers`. Operators are validated against the live catalog (see the OpenAPI `SegmentCondition` schema).
### Campaigns (`campaigns`, send via `sends`)
CRUD at `/campaigns`; `POST /campaigns/{uid}/run` queues the send (scope `sends`); `pause`/`resume`; CSV logs at `/campaigns/{uid}/{tracking,open,click,bounce,feedback,unsubscribe}-log/download`.
### AI email generation (`emails`)
`POST /emails/generate` (prompt -> saved template + HTML, ~15-60s, send an Idempotency-Key) and `PATCH /emails/{template_uid}` (natural-language edit with checkpoint). Templates open in the visual builder afterwards.
### Templates (`templates`)
Read-only: `GET /templates` (filter `search`, `source=gallery|ai|imported|api`), `GET /templates/{uid}?include=html`.
### Automations (`automations`)
Graph CRUD at `/automations` (create from a validated graph OR an AI `prompt`); atomic batch edits via `PATCH` operations; `activate`/`deactivate`; fire per-contact runs with `POST /automations/{uid}/runs`; cancel via `PATCH /automation-runs/{uid}`.
### Trigger events (`triggers`)
Named events your application fires: CRUD at `/triggers` (the slug `name` is immutable; `stripe.*`/`shopify.*` are reserved for provider sources); `POST /triggers/{uid}/fire` enrolls every listening automation with the payload as `{{ event.* }}` merge tags; audit at `GET /triggers/{uid}/fires` (90-day retention).
### Transactional (`sends`)
Receipts/notifications on an isolated stream: `POST /transactional/send` (`to`, `subject`, `from_email`, `template_uid` OR `html`, `merge_data`?) queues and returns `{message_uid, status: "queued"}`; suppressed recipients are refused synchronously 409 with `details.suppression`; log at `GET /transactional` (+ `/{uid}` detail with events timeline).
### Sending domains (`domains`)
`POST /domains` registers and returns the DNS records to publish; `POST /domains/{uid}/verify` re-checks DNS; deletion is blocked 409 while scheduled/sending campaigns use the domain.
### Webhooks (`webhooks`)
`POST /webhooks` subscribes an https endpoint to events (secret returned once); deliveries are HMAC-signed (`X-EmailFlow-Signature: t=...,v1=...`), retried [30s, 5m, 30m], auto-disabled after 20 consecutive failures; `POST /webhooks/{uid}/test` pings synchronously.
### Brand (`brand`)
Read-only `GET /brand`: company profile, colors, fonts, logo URL, and `crawl_status` to poll after a brand scan.
### Usage (`usage`)
Read-only `GET /usage`: plan quota meters (used/limit/unlimited/granted) and credit snapshot (sends, AI tokens, verifications).
### Analytics (`analytics`)
`GET /analytics/events`: unified feed of sent/failed/open/click/bounce/feedback/unsubscribe events, cursor-paginated, filterable by type/campaign/email/time range.
## Canonical examples
Generate an email from a prompt:
curl -X POST https://emailflow.ai/api/v1/emails/generate \
-H "Authorization: Bearer efa_your_key_here" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: 1b9d6bcd-bbfd-4b2d-9b5d-ab8dfbbd4bed" \
-d '{"prompt": "Welcome email for a coffee subscription, warm tone"}'
Create a segment of recently-engaged contacts:
curl -X POST https://emailflow.ai/api/v1/lists/LIST_UID/segments \
-H "X-API-Key: efa_your_key_here" \
-H "Content-Type: application/json" \
-d '{"name": "Engaged 30d", "matching": "all", "conditions": [{"type": "last_open_email", "operator": "last_open_email_less_than_days", "value": "30"}]}'
Register a sending domain (then publish the returned DNS records and verify):
curl -X POST https://emailflow.ai/api/v1/domains \
-H "Authorization: Bearer efa_your_key_here" \
-H "Content-Type: application/json" \
-d '{"domain": "mail.example.com"}'
Fire an automation for one contact:
curl -X POST https://emailflow.ai/api/v1/automations/AUTOMATION_UID/runs \
-H "Authorization: Bearer efa_your_key_here" \
-H "Content-Type: application/json" \
-d '{"email": "person@example.com"}'
Register a webhook (store the returned secret; it is shown once):
curl -X POST https://emailflow.ai/api/v1/webhooks \
-H "Authorization: Bearer efa_your_key_here" \
-H "Content-Type: application/json" \
-d '{"url": "https://example.com/hooks/emailflow", "events": ["email.delivered", "contact.subscribed"]}'