Resource reference
Every core resource over plain REST: lists, subscribers (single and bulk), segments, campaigns with downloadable logs, sending domains, templates, your brand profile, account usage meters, and a unified analytics event feed. Scope names in each section refer to API key scopes.
Lists
Mailing lists are the primary container for subscribers. Each list has a default sender identity and a set of custom fields, which you add with the add-field sub-route. Scope: audiences (implied by contacts).
| Method | Endpoint | Description |
|---|---|---|
| GET | /lists | Paginated index of lists owned by the key's account. |
| POST | /lists | Create a list. Required: name, from_email, from_name. |
| GET | /lists/{uid} | Fetch a single list with its fields and statistics. |
| POST | /lists/{uid}/add-field | Add a custom field. type is one of text, number, datetime. |
| DELETE | /lists/{uid} | Permanently delete a list and its subscribers. |
# Create a list
curl -X POST https://emailflow.ai/api/v1/lists \
-H "Authorization: Bearer efa_YOUR_KEY" \
-d name="Newsletter" \
-d from_name="Acme" \
-d from_email="hi@acme.com" \
-d contact[company]="Acme Inc." \
-d contact[address_1]="1 Market St" \
-d contact[city]="San Francisco" \
-d contact[zip]="94105" \
-d contact[phone]="+1 555 0100" \
-d contact[country_id]=1 \
-d contact[email]="hi@acme.com"
# Response
{
"status": 1,
"message": "List has been created!",
"list_uid": "ab12cd34ef"
}
Add a custom field to that list:
curl -X POST https://emailflow.ai/api/v1/lists/{uid}/add-field \
-H "Authorization: Bearer efa_YOUR_KEY" \
-d type=text \
-d label="First name" \
-d tag=FIRST_NAME
Subscribers
Subscribers belong to a list and carry custom field values, tags, and a status of subscribed, unsubscribed, or unconfirmed. Custom fields are passed as uppercase parameters (FIRST_NAME, LAST_NAME, …) matching your list's field configuration. Scope: audiences (implied by contacts).
Single subscriber operations
| Method | Endpoint | Description |
|---|---|---|
| GET | /subscribers?list_uid={uid} | Paginated index for a list. Filter with open=yes|no and click=yes|no. |
| POST | /subscribers | Create a subscriber. Required: list_uid, EMAIL. |
| GET | /subscribers/{id} | Fetch a single subscriber by id or email. |
| PATCH | /subscribers/{id} | Update fields, tags, or status. |
| POST | /subscribers/{id}/add-tag | Add comma-separated tags. |
| POST | /subscribers/{id}/remove-tag | Remove comma-separated tags. |
| GET | /subscribers/email/{email} | Find every list containing a subscriber with this email. |
| PATCH | /lists/{list_uid}/subscribers/{id}/subscribe | Mark the subscriber subscribed. |
| PATCH | /lists/{list_uid}/subscribers/{id}/unsubscribe | Mark the subscriber unsubscribed. |
| PATCH | /lists/{list_uid}/subscribers/email/{email}/unsubscribe | Unsubscribe by email when you do not have the id. |
| DELETE | /subscribers/{id} | Permanently delete the subscriber from the list. |
# Add a subscriber with custom fields and tags curl -X POST https://emailflow.ai/api/v1/subscribers \ -H "Authorization: Bearer efa_YOUR_KEY" \ -d list_uid=ab12cd34ef \ -d EMAIL=alice@example.com \ -d FIRST_NAME=Alice \ -d tag=vip,newsletter \ -d status=subscribed
Bulk import and delete
Import or remove many subscribers at once. The synchronous endpoints apply rows inline and return per-row results; JSON payloads accept at most 1,000 rows per request (a larger payload returns 422). For bigger imports, upload a CSV to the asynchronous endpoint, which queues the work and returns a job you poll for progress. Write endpoints carry the batch rate-limit class (10 requests per minute) — see rate limits.
| Method | Endpoint | Description |
|---|---|---|
| POST | /lists/{list_uid}/subscribers/bulk | Synchronous bulk import (≤1,000 rows). Returns counts and per-row errors. |
| DELETE | /lists/{list_uid}/subscribers/bulk | Batch delete by email (≤1,000). Body {"emails": [...]}; unknown emails come back as warnings, not errors. |
| POST | /lists/{list_uid}/subscribers/bulk/async | Queued bulk import. Accepts a JSON body (≤1,000 rows) or a multipart CSV upload (unbounded). Returns 202 with a job. |
| GET | /lists/{list_uid}/import-jobs | List recent bulk-import jobs for a list. |
| GET | /import-jobs/{job_uid} | Poll a job for status, progress, and counts. |
| DELETE | /import-jobs/{job_uid} | Cancel a queued or running job. |
Callers authenticating with a scoped efa_ key receive the aligned partial-success shape {processed, created, updated, failed, errors: [{index, email, code, message}], warnings: [...]}; legacy-token callers keep the original {list_uid, received, inserted, updated, failed, batch_id, errors} shape unchanged.
# Synchronous bulk import (JSON body)
curl -X POST "https://emailflow.ai/api/v1/lists/{list_uid}/subscribers/bulk" \
-H "Authorization: Bearer efa_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{
"mode": "overwrite",
"subscribers": [
{ "EMAIL": "alice@example.com", "FIRST_NAME": "Alice", "tags": ["vip"] },
{ "EMAIL": "bob@example.com", "FIRST_NAME": "Bob", "status": "subscribed" }
]
}'
# Response (scoped efa_ key)
{
"processed": 2,
"created": 1,
"updated": 1,
"failed": 0,
"errors": [],
"warnings": []
}
For large or untrusted inputs, queue the job and poll it:
# Queue an async import, then poll
curl -X POST "https://emailflow.ai/api/v1/lists/{list_uid}/subscribers/bulk/async" \
-H "Authorization: Bearer efa_YOUR_KEY" \
-F "file=@subscribers.csv" \
-F 'mapping={"Email":"EMAIL","First Name":"FIRST_NAME"}' \
-F "mode=overwrite"
# 202 Accepted
{ "job_uid": "job_77aa", "status": "queued", "status_url": "...", "cancel_url": "..." }
# Poll
curl "https://emailflow.ai/api/v1/import-jobs/job_77aa" \
-H "Authorization: Bearer efa_YOUR_KEY"
Segments
Segments are saved filters over a list's subscribers, built from conditions on fields, tags, verification state, engagement dates, and signup date. Conditions use the same operators the in-app segment builder offers; an invalid operator for a condition type is rejected with a validation error. Scope: contacts.
| Method | Endpoint | Description |
|---|---|---|
| GET | /lists/{list_uid}/segments | Paginated index of a list's segments with matching-subscriber counts. |
| POST | /lists/{list_uid}/segments | Create a segment. Required: name, matching (all or any), conditions[]. |
| GET | /segments/{uid} | Fetch one segment with its conditions. |
| PATCH | /segments/{uid} | Update name, matching mode, or conditions (conditions replace atomically). |
| DELETE | /segments/{uid} | Delete the segment. Subscribers are not affected. |
| GET | /segments/{uid}/subscribers | Paginated subscribers currently matching the segment. |
# Create a segment of recently active Gmail contacts
curl -X POST "https://emailflow.ai/api/v1/lists/{list_uid}/segments" \
-H "Authorization: Bearer efa_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Active Gmail readers",
"matching": "any",
"conditions": [
{ "type": "field", "field_uid": "EMAIL_FIELD_UID", "operator": "contains", "value": "@gmail.com" },
{ "type": "last_open_email", "operator": "last_open_email_less_than_days", "value": "7" }
]
}'
Campaigns
Campaigns are the unit of sending. Create one with HTML content and tracking flags, move it through run / pause / resume, then download per-event logs as CSV. See the campaigns guide for the full picture. Scope: sends (implied by emails).
Managing campaigns
| Method | Endpoint | Description |
|---|---|---|
| GET | /campaigns | Paginated index. Accepts page and per_page. |
| POST | /campaigns | Create a campaign. Required: list_uid, name, subject, from_email, from_name. |
| GET | /campaigns/{uid} | Fetch a single campaign with statistics. |
| PATCH | /campaigns/{uid} | Update content, subject, or tracking flags. |
| POST | /campaigns/{uid}/run | Queue the campaign for delivery. |
| POST | /campaigns/{uid}/pause | Pause an in-flight campaign. |
| POST | /campaigns/{uid}/resume | Resume a paused campaign. |
| DELETE | /campaigns/{uid} | Delete a campaign that is not in progress. |
# Create then run a campaign
curl -X POST https://emailflow.ai/api/v1/campaigns \
-H "Authorization: Bearer efa_YOUR_KEY" \
-d list_uid=ab12cd34ef \
-d name="Spring sale" \
-d subject="20% off this week" \
-d from_email=hi@acme.com \
-d from_name="Acme" \
-d track_open=true \
-d track_click=true \
--data-urlencode 'html=<p>Hello!</p>'
curl -X POST https://emailflow.ai/api/v1/campaigns/{uid}/run \
-H "Authorization: Bearer efa_YOUR_KEY"
Log downloads
Each log endpoint streams a CSV for one event type. See reports and logs for how this data maps to the in-app reports. For a streaming JSON feed of the same events, use the analytics event feed below; to be notified the moment events happen, use webhooks.
| Method | Endpoint | Description |
|---|---|---|
| GET | /campaigns/{uid}/tracking-log/download | Combined tracking log. |
| GET | /campaigns/{uid}/open-log/download | Opens. |
| GET | /campaigns/{uid}/click-log/download | Clicks. |
| GET | /campaigns/{uid}/bounce-log/download | Bounces. |
| GET | /campaigns/{uid}/feedback-log/download | Spam and abuse feedback. |
| GET | /campaigns/{uid}/unsubscribe-log/download | Unsubscribes. |
Sending domains
Register and verify the domains you send from. Creating a domain returns the DNS records to publish (an ownership record, DKIM records, and SPF); call verify after publishing to re-poll status. Depending on your plan's sending configuration, records are either the managed CNAME set or TXT-based local DKIM — the response reflects whichever mode applies. Internationalized domains are stored in punycode. Scope: domains (implied by emails).
| Method | Endpoint | Description |
|---|---|---|
| GET | /domains | Paginated index with per-record verification status. |
| POST | /domains | Register a domain. Required: domain. Returns the DNS record set to publish. |
| POST | /domains/{uid}/verify | Re-check verification and return updated per-record status. |
| DELETE | /domains/{uid} | Remove the domain. Returns 409 while scheduled or sending campaigns use it. |
# Register a domain, publish the returned records, then verify
curl -X POST https://emailflow.ai/api/v1/domains \
-H "Authorization: Bearer efa_YOUR_KEY" \
-d domain=mail.acme.com
curl -X POST https://emailflow.ai/api/v1/domains/{uid}/verify \
-H "Authorization: Bearer efa_YOUR_KEY"
Templates
Read access to your saved email templates. source tells you where each came from: gallery (started from the template gallery), imported (ZIP or HTML upload), ai (built in the AI builder), or api (created through the AI generation API). Pass include=html on the detail endpoint to receive the full HTML. Scope: templates (implied by emails).
| Method | Endpoint | Description |
|---|---|---|
| GET | /templates | Paginated index. Filters: search (name), source (gallery, ai, imported, api). |
| GET | /templates/{uid} | Fetch one template. Add ?include=html for the full HTML body. |
Brand profile
Your account's brand profile — company identity, colors, fonts, and logo — as one read-only resource. Poll crawl_status after starting a brand scan in the app (idle → scraping → analyzing → importing_logo → extracting_colors → completed or failed). Scope: brand.
| Method | Endpoint | Description |
|---|---|---|
| GET | /brand | The account's brand profile. 404 until a brand scan or manual profile exists. |
Usage
One consolidated snapshot of your plan quotas and credits: per-resource used/limit meters (lists, campaigns, subscribers, automations, sending domains, users, API keys) and per-credit granted/used/remaining balances (email sends, contact verifications, AI generations). Unlimited grants report "unlimited": true. Scope: usage.
| Method | Endpoint | Description |
|---|---|---|
| GET | /usage | Plan summary plus all quota and credit meters in one response. |
Analytics events
A unified, newest-first feed of every delivery event across your account: sent, failed, open, click, bounce, feedback, and unsubscribe. Each event carries the campaign, the subscriber email, and type-specific metadata (clicked URL, bounce type, IP and user agent). Filters compose; results paginate with an opaque cursor returned in meta.next_cursor. Scope: analytics.
| Method | Endpoint | Description |
|---|---|---|
| GET | /analytics/events | Unified event feed. Filters: type[], campaign_uid, email, since, until (ISO 8601); paginate with per_page + cursor. |
# Recent opens and clicks for one campaign
curl "https://emailflow.ai/api/v1/analytics/events?type[]=open&type[]=click&campaign_uid={uid}" \
-H "Authorization: Bearer efa_YOUR_KEY"
# Response
{
"data": [
{
"type": "click",
"occurred_at": "2026-06-12T09:15:02+00:00",
"campaign_uid": "ab12cd34ef",
"campaign_name": "Spring sale",
"subscriber_email": "alice@example.com",
"meta": { "url": "https://acme.com/promo", "ip_address": "198.51.100.7", "user_agent": "..." }
}
],
"meta": { "next_cursor": "eyJ0IjoiMjAyNi0wNi0xMiAw..." }
}
Notifications
If you operate your own delivery pipeline, push delivery feedback back into EmailFlow AI so reports and suppression stay accurate. Report hard bounces and spam or abuse complaints against the original message id.
| Method | Endpoint | Description |
|---|---|---|
| POST | /notification/bounce | Record a hard bounce. Parameters: message_id, description. |
| POST | /notification/feedback | Record a feedback report. Parameters: message_id, type (spam or abuse), description. |
curl -X POST https://emailflow.ai/api/v1/notification/bounce \ -H "Authorization: Bearer efa_YOUR_KEY" \ -d message_id="2016374...@example.com" \ -d description="550 mailbox not found"