Download OpenAPI specification:Download
SendFox's REST API lets you manage contacts, campaigns, lists, automations, and forms programmatically. It uses OAuth 2.0 for authentication.
Compatible with AI agents (Claude, ChatGPT, etc.) via the OpenAPI spec. Available at GET /openapi.yaml.
Create a personal access token at https://sendfox.com/account/oauth. Once created, use it in the Authorization header:
Authorization: Bearer {TOKEN}
For integrations that require user authentication, create an OAuth 2.0 client at https://sendfox.com/account/oauth
API requests are limited to 60 requests per minute per authenticated user. Rate limit status is returned in response headers:
X-RateLimit-Limit: Maximum requests per minuteX-RateLimit-Remaining: Remaining requests in current windowRetry-After: Seconds until rate limit resets (only on 429 responses)All error responses use standard HTTP status codes and Laravel's default error format:
message: Human-readable error descriptionerrors: Field-level validation errors (on 422 responses)API access requires a Lifetime or Empire plan. Free users cannot use the API.
Accounts restricted by SendFox cannot use authenticated API endpoints. These requests return 403 Forbidden with the error code account_restricted and a link to the account status page.
Returns a paginated list of contacts (100 per page by default, up to 1000 via per_page).
Supports engagement filtering through filter[...] query parameters, so you can answer
questions like "who last opened over a year ago" without paging the whole account. All
filter conditions are AND-ed. Pass count_only=true to get just the number of matches —
the cheapest way to size an audience before acting on it.
| query | string Search query for filtering contacts |
| unsubscribed | boolean Filter unsubscribed contacts |
string Filter by specific email | |
| per_page | integer [ 1 .. 1000 ] Default: 100 Contacts per page |
| count_only | boolean Return only the number of matching contacts, with no contact records |
| filter[status] | string Enum: "active" "engaged" "inactive" "new" "unconfirmed" "unsubscribed" "bounced" "invalid" Engagement status |
| filter[last_opened_after] | string <date-time> Contacts whose most recent open is on or after this date |
| filter[last_opened_before] | string <date-time> Contacts whose most recent open is before this date. Excludes contacts who never opened — use filter[never_opened] for those. |
| filter[last_clicked_after] | string <date-time> Contacts whose most recent click is on or after this date |
| filter[last_clicked_before] | string <date-time> Contacts whose most recent click is before this date |
| filter[last_sent_after] | string <date-time> Contacts last sent to on or after this date |
| filter[last_sent_before] | string <date-time> Contacts last sent to before this date |
| filter[created_after] | string <date-time> Contacts created on or after this date |
| filter[created_before] | string <date-time> Contacts created before this date |
| filter[never_opened] | boolean Only contacts who have never opened an email |
| filter[never_clicked] | boolean Only contacts who have never clicked a link |
| filter[never_sent] | boolean Only contacts who have never been sent an email |
| filter[in_list_ids] | Array of integers Only contacts still subscribed to any of these lists. Members who unsubscribed from the list are not matched, so results may be smaller than before per-list unsubscribe shipped. |
| filter[not_in_list_ids] | Array of integers Exclude contacts who are members of any of these lists, whether or not they unsubscribed from it. |
| filter[tag_ids] | Array of integers Only contacts still subscribed to any of these tags. Somebody who opted out of the tag keeps it but is not matched. Same rule as filter[in_list_ids]; see the tag_ids note in the bulk-action filter schema. |
| filter[not_tag_ids] | Array of integers Exclude contacts carrying any of these tags |
{- "count": 0,
- "filter": "string"
}| email required | string <email> |
| first_name | string |
| last_name | string |
| ip_address | string |
| lists | Array of integers Array of list IDs to add the contact to |
Array of objects |
{- "first_name": "string",
- "last_name": "string",
- "ip_address": "string",
- "lists": [
- 0
], - "contact_fields": [
- {
- "name": "string",
- "value": "string"
}
]
}{- "id": 0,
- "first_name": "string",
- "last_name": "string",
- "ip_address": "string",
- "unsubscribed_at": "2019-08-24T14:15:22Z",
- "created_at": "2019-08-24T14:15:22Z",
- "updated_at": "2019-08-24T14:15:22Z"
}{- "id": 0,
- "first_name": "string",
- "last_name": "string",
- "ip_address": "string",
- "unsubscribed_at": "2019-08-24T14:15:22Z",
- "created_at": "2019-08-24T14:15:22Z",
- "updated_at": "2019-08-24T14:15:22Z"
}Update contact details including name, list memberships, and custom fields
| id required | integer |
| first_name | string |
| last_name | string |
| lists | Array of integers Array of list IDs (replaces all current list memberships) |
Array of objects |
{- "first_name": "string",
- "last_name": "string",
- "lists": [
- 0
], - "contact_fields": [
- {
- "name": "string",
- "value": "string"
}
]
}{- "id": 0,
- "first_name": "string",
- "last_name": "string",
- "ip_address": "string",
- "unsubscribed_at": "2019-08-24T14:15:22Z",
- "created_at": "2019-08-24T14:15:22Z",
- "updated_at": "2019-08-24T14:15:22Z"
}Returns paginated email deliverables and contact-level engagement summary
| id required | integer |
{- "contact": {
- "last_sent_at": "2019-08-24T14:15:22Z",
- "last_opened_at": "2019-08-24T14:15:22Z",
- "last_clicked_at": "2019-08-24T14:15:22Z",
- "unsubscribed_at": "2019-08-24T14:15:22Z",
- "bounced_at": "2019-08-24T14:15:22Z"
}, - "deliverables": {
- "data": [
- {
- "campaign_title": "string",
- "campaign_id": 0,
- "sent_at": "2019-08-24T14:15:22Z",
- "opened_at": "2019-08-24T14:15:22Z",
- "clicked_at": "2019-08-24T14:15:22Z",
- "bounced_at": "2019-08-24T14:15:22Z",
- "unsubscribed_at": "2019-08-24T14:15:22Z",
- "spam_at": "2019-08-24T14:15:22Z"
}
]
}
}Import up to 1,000 contacts in a single request. Creates new contacts or updates existing ones.
required | Array of objects <= 1000 items |
{- "contacts": [
]
}{- "created": 0,
- "updated": 0
}Queues one action against every contact the filter matches, applied in chunks in the background. Returns immediately with an id to poll.
Run with dry_run: true first. A dry run reports how many contacts match and changes
nothing — it is the only way to catch a filter that matches more than intended, and a bulk
write is not undoable. An empty filter (which would match the whole account) is rejected
unless it is a dry run.
Per-campaign conditions (opened_campaign_id, not_opened_campaign_id,
clicked_campaign_id) are available here but not on GET /contacts, because they are
resolved in the background rather than inside a request.
Bulk delete and bulk unsubscribe are deliberately not offered.
Because in_list_ids matches only subscribed members, remove_from_list with
filter.in_list_ids set to that same list leaves the memberships of people who
unsubscribed from it in place. They already receive nothing from that list.
tag_ids behaves the same way, so remove_tag with filter.tag_ids set to that
same tag leaves the tag on people who unsubscribed from it. They already receive
nothing sent to that tag. To reach every carrier regardless of opt-out status,
select them by some other filter, or read them from the tag endpoints.
| action required | string Enum: "apply_tag" "remove_tag" "add_to_list" "remove_from_list" |
| target_id required | integer The tag id (apply_tag/remove_tag) or list id (add_to_list/remove_from_list) |
| dry_run | boolean Default: false When true, only count the matches — nothing is modified |
object (ContactFilter) An engagement filter over the account's contacts. Every condition is AND-ed. Omitting the object entirely means "all contacts". |
{- "action": "apply_tag",
- "target_id": 0,
- "dry_run": false,
- "filter": {
- "status": "active",
- "last_opened_after": "2019-08-24T14:15:22Z",
- "last_opened_before": "2019-08-24T14:15:22Z",
- "last_clicked_after": "2019-08-24T14:15:22Z",
- "last_clicked_before": "2019-08-24T14:15:22Z",
- "last_sent_after": "2019-08-24T14:15:22Z",
- "last_sent_before": "2019-08-24T14:15:22Z",
- "created_after": "2019-08-24T14:15:22Z",
- "created_before": "2019-08-24T14:15:22Z",
- "never_opened": true,
- "never_clicked": true,
- "never_sent": true,
- "in_list_ids": [
- 0
], - "not_in_list_ids": [
- 0
], - "tag_ids": [
- 0
], - "not_tag_ids": [
- 0
], - "opened_campaign_id": 0,
- "not_opened_campaign_id": 0,
- "clicked_campaign_id": 0
}
}{- "id": 0,
- "status": "pending",
- "action": "apply_tag",
- "target_id": 0,
- "dry_run": true,
- "filter": "string",
- "matched_count": 0,
- "processed_count": 0,
- "error": "string",
- "started_at": "2019-08-24T14:15:22Z",
- "completed_at": "2019-08-24T14:15:22Z"
}For a dry run, matched_count is the answer and nothing was modified. A run whose match set exceeds the per-request ceiling fails without applying anything.
| id required | integer |
{- "id": 0,
- "status": "pending",
- "action": "apply_tag",
- "target_id": 0,
- "dry_run": true,
- "filter": "string",
- "matched_count": 0,
- "processed_count": 0,
- "error": "string",
- "started_at": "2019-08-24T14:15:22Z",
- "completed_at": "2019-08-24T14:15:22Z"
}| query | string Search query for filtering contacts |
{- "data": [
- {
- "id": 0,
- "first_name": "string",
- "last_name": "string",
- "ip_address": "string",
- "unsubscribed_at": "2019-08-24T14:15:22Z",
- "created_at": "2019-08-24T14:15:22Z",
- "updated_at": "2019-08-24T14:15:22Z"
}
], - "current_page": 0,
- "total": 0,
- "per_page": 0
}| email required | string <email> |
{- "email": "[email protected]"
}{- "id": 0,
- "first_name": "string",
- "last_name": "string",
- "ip_address": "string",
- "unsubscribed_at": "2019-08-24T14:15:22Z",
- "created_at": "2019-08-24T14:15:22Z",
- "updated_at": "2019-08-24T14:15:22Z"
}{- "data": [
- {
- "id": 0,
- "title": "string",
- "subject": "string",
- "preview_text": "string",
- "html": "string",
- "from_name": "string",
- "scheduled_at": "2019-08-24T14:15:22Z",
- "sent_at": "2019-08-24T14:15:22Z",
- "timezone": "string",
- "is_web_gated": true,
- "created_at": "2019-08-24T14:15:22Z",
- "updated_at": "2019-08-24T14:15:22Z"
}
], - "current_page": 0,
- "total": 0,
- "per_page": 0
}Creates a campaign as a draft. To send it, use the send endpoint or provide scheduled_at. Subject lines cannot start with "RE:" or "FWD:". At least one list is required if scheduled_at is provided.
| title required | string <= 191 characters |
| subject required | string <= 191 characters |
| preview_text | string or null <= 191 characters Inbox preview snippet shown beneath the subject line. Optional. |
| html required | string <= 1000000 characters Email body HTML content |
| from_name required | string <= 191 characters |
| from_email required | string <email> <= 191 characters |
| scheduled_at | string <date-time> Schedule send time (omit for draft). Must include at least one list. |
| lists | Array of integers Array of list IDs to send to |
| excluded_lists | Array of integers List IDs whose contacts should be excluded from the send. Exclusion wins over inclusion. |
| to_contact_tags | Array of integers Tag IDs whose contacts should receive the campaign. Can be combined with lists. |
| excluded_contact_tags | Array of integers Tag IDs whose contacts should be excluded from the send. Exclusion wins over inclusion. |
| web_publish | boolean Publish the email to the web on your primary Smart Page once it sends. Requires a Smart Page. Omit it to list the email on every Smart Page whose Newsletters tab is on; send false to keep it off the web. |
| web_gate | boolean Default: false On a published email, ask new readers for their email address to read past the opening. |
{- "title": "string",
- "subject": "string",
- "preview_text": "string",
- "html": "string",
- "from_name": "string",
- "scheduled_at": "2019-08-24T14:15:22Z",
- "lists": [
- 0
], - "excluded_lists": [
- 0
], - "to_contact_tags": [
- 0
], - "excluded_contact_tags": [
- 0
], - "web_publish": true,
- "web_gate": false
}{- "id": 0,
- "title": "string",
- "subject": "string",
- "preview_text": "string",
- "html": "string",
- "from_name": "string",
- "scheduled_at": "2019-08-24T14:15:22Z",
- "sent_at": "2019-08-24T14:15:22Z",
- "timezone": "string",
- "is_web_gated": true,
- "created_at": "2019-08-24T14:15:22Z",
- "updated_at": "2019-08-24T14:15:22Z"
}Returns stored counts and rates for one to 100 distinct owned campaigns in request order. The entire request fails with the same validation error for missing, deleted or foreign IDs. Does not return link stats or query individual delivery events.
| campaign_ids[] required | Array of integers [ 1 .. 100 ] items unique [ items >= 1 ] Repeat campaign_ids[] for each campaign ID. |
{- "data": [
- {
- "link_stats": [
- {
- "url": "string",
- "clicks": 0
}
], - "sent_count": 0,
- "unique_open_count": 0,
- "unique_click_count": 0,
- "unsubscribe_count": 0,
- "bounce_count": 0,
- "spam_count": 0,
- "open_rate": 0,
- "click_rate": 0,
- "unsubscribe_rate": 0,
- "bounce_rate": 0,
- "spam_rate": 0,
- "id": 0
}
]
}{- "id": 0,
- "title": "string",
- "subject": "string",
- "preview_text": "string",
- "html": "string",
- "from_name": "string",
- "scheduled_at": "2019-08-24T14:15:22Z",
- "sent_at": "2019-08-24T14:15:22Z",
- "timezone": "string",
- "is_web_gated": true,
- "created_at": "2019-08-24T14:15:22Z",
- "updated_at": "2019-08-24T14:15:22Z"
}Only draft campaigns (not yet sent) can be updated. All fields are optional.
| id required | integer |
| title | string <= 191 characters |
| subject | string <= 191 characters |
| preview_text | string or null <= 191 characters Inbox preview snippet. Pass null to clear. |
| html | string <= 1000000 characters |
| from_name | string <= 191 characters |
| from_email | string <email> <= 191 characters |
| scheduled_at | string or null <date-time> Set to null to unschedule, or a datetime to schedule |
| lists | Array of integers Replaces all list assignments |
| excluded_lists | Array of integers Replaces the excluded lists (contacts in these lists are removed from the send). |
| to_contact_tags | Array of integers Replaces the included tag audience (contacts carrying these tags receive the campaign). |
| excluded_contact_tags | Array of integers Replaces the excluded tag audience (contacts carrying these tags are removed from the send). |
{- "title": "string",
- "subject": "string",
- "preview_text": "string",
- "html": "string",
- "from_name": "string",
- "scheduled_at": "2019-08-24T14:15:22Z",
- "lists": [
- 0
], - "excluded_lists": [
- 0
], - "to_contact_tags": [
- 0
], - "excluded_contact_tags": [
- 0
]
}{- "id": 0,
- "title": "string",
- "subject": "string",
- "preview_text": "string",
- "html": "string",
- "from_name": "string",
- "scheduled_at": "2019-08-24T14:15:22Z",
- "sent_at": "2019-08-24T14:15:22Z",
- "timezone": "string",
- "is_web_gated": true,
- "created_at": "2019-08-24T14:15:22Z",
- "updated_at": "2019-08-24T14:15:22Z"
}Schedules a draft campaign for immediate sending. The campaign must:
All existing abuse prevention applies automatically: content approval workflow, sending throttles, spam detection, and bounce rate monitoring.
| id required | integer |
{- "id": 0,
- "title": "string",
- "subject": "string",
- "preview_text": "string",
- "html": "string",
- "from_name": "string",
- "scheduled_at": "2019-08-24T14:15:22Z",
- "sent_at": "2019-08-24T14:15:22Z",
- "timezone": "string",
- "is_web_gated": true,
- "created_at": "2019-08-24T14:15:22Z",
- "updated_at": "2019-08-24T14:15:22Z"
}Delivers the rendered campaign to the addresses given so it can be checked in a real inbox. This is a real send to those addresses only: the campaign's audience is untouched and the campaign stays a draft. Recipients are added to the account's "My Test Contacts" list.
Limits: up to 30 addresses per call (1 on the free plan) and 30 test sends per
account per day, shared with Send Test in the SendFox app. A test from a newly
created account goes to the account's own address only, unless the campaign sends
from a domain the account has verified; notice in the response says so when
addresses were dropped.
| id required | integer |
| emails required | Array of strings <email> [ 1 .. 30 ] items Addresses to deliver the test to. Trimmed and de-duplicated. |
{
}{- "sent": true,
- "campaign_id": 0,
- "notice": "string"
}Returns sent count and open/click/bounce/unsubscribe/spam counts and rates, all read from stored counters.
Pass include_link_stats=true to also get link_stats — the campaign's links ranked by click count. That part is opt-in because it counts rows in email_link_clicks once per link rather than reading a counter, so leaving it off keeps this endpoint as cheap as it has always been.
| id required | integer |
| include_link_stats | boolean Default: false When true, include the link_stats breakdown in the response |
| link_limit | integer [ 1 .. 100 ] Default: 50 How many links to return in link_stats, most-clicked first. Ignored unless include_link_stats is true. |
{- "link_stats": [
- {
- "url": "string",
- "clicks": 0
}
], - "sent_count": 0,
- "unique_open_count": 0,
- "unique_click_count": 0,
- "unsubscribe_count": 0,
- "bounce_count": 0,
- "spam_count": 0,
- "open_rate": 0,
- "click_rate": 0,
- "unsubscribe_rate": 0,
- "bounce_rate": 0,
- "spam_rate": 0
}Returns the contacts in one of a sent campaign's engagement groups. non_openers covers contacts the campaign actually sent to who did not open it — queued and cancelled deliverables are excluded, since they never had the chance.
| id required | integer |
| type required | string Enum: "openers" "clickers" "non_openers" "bounced" "unsubscribed" Which engagement group to list |
| per_page | integer [ 1 .. 1000 ] Default: 100 Records per page |
{- "data": [
- {
- "id": 0,
- "contact_id": 0,
- "email": "string",
- "first_name": "string",
- "last_name": "string",
- "sent_at": "2019-08-24T14:15:22Z",
- "opened_at": "2019-08-24T14:15:22Z",
- "clicked_at": "2019-08-24T14:15:22Z",
- "bounced_at": "2019-08-24T14:15:22Z",
- "unsubscribed_at": "2019-08-24T14:15:22Z"
}
], - "current_page": 0,
- "total": 0,
- "per_page": 0
}Creates a new draft with the original's content, sender, and exclusions, aimed at the chosen slice of the original audience. Omit scheduled_at to leave it as a draft. The source campaign's lists are deliberately not carried over — the engagement segment is the audience.
| id required | integer |
| audience required | string Enum: "non_openers" "openers" "clickers" |
| subject | string <= 191 characters Optional new subject line; defaults to the original's |
| title | string <= 191 characters Optional internal name; defaults to "Resend: |
| scheduled_at | string <date-time> Optional send time; omit to leave the resend as a draft |
{- "audience": "non_openers",
- "subject": "string",
- "title": "string",
- "scheduled_at": "2019-08-24T14:15:22Z"
}{- "id": 0,
- "title": "string",
- "subject": "string",
- "preview_text": "string",
- "html": "string",
- "from_name": "string",
- "scheduled_at": "2019-08-24T14:15:22Z",
- "sent_at": "2019-08-24T14:15:22Z",
- "timezone": "string",
- "is_web_gated": true,
- "created_at": "2019-08-24T14:15:22Z",
- "updated_at": "2019-08-24T14:15:22Z"
}| query | string Search query for filtering lists |
{- "data": [
- {
- "id": 0,
- "name": "string",
- "user_id": 0,
- "subscribed_contacts_count": 0,
- "unsubscribed_contacts_count": 0,
- "public_description": "string",
- "average_email_open_percent": 0,
- "average_email_click_percent": 0,
- "created_at": "2019-08-24T14:15:22Z",
- "updated_at": "2019-08-24T14:15:22Z"
}
], - "current_page": 0,
- "total": 0,
- "per_page": 0
}| name required | string |
{- "name": "string"
}{- "id": 0,
- "name": "string",
- "user_id": 0,
- "subscribed_contacts_count": 0,
- "unsubscribed_contacts_count": 0,
- "public_description": "string",
- "average_email_open_percent": 0,
- "average_email_click_percent": 0,
- "created_at": "2019-08-24T14:15:22Z",
- "updated_at": "2019-08-24T14:15:22Z"
}Returns list details including average open and click rates
| id required | integer |
{- "id": 0,
- "name": "string",
- "user_id": 0,
- "subscribed_contacts_count": 0,
- "unsubscribed_contacts_count": 0,
- "public_description": "string",
- "average_email_open_percent": 0,
- "average_email_click_percent": 0,
- "created_at": "2019-08-24T14:15:22Z",
- "updated_at": "2019-08-24T14:15:22Z"
}| id required | integer |
| name required | string <= 191 characters |
{- "name": "string"
}{- "id": 0,
- "name": "string",
- "user_id": 0,
- "subscribed_contacts_count": 0,
- "unsubscribed_contacts_count": 0,
- "public_description": "string",
- "average_email_open_percent": 0,
- "average_email_click_percent": 0,
- "created_at": "2019-08-24T14:15:22Z",
- "updated_at": "2019-08-24T14:15:22Z"
}Members who no longer receive this list, and when they stopped. Each row is a Contact with two extra fields.
The two timestamps are never merged. list_unsubscribed_at is this list only. unsubscribed_at keeps the meaning it has everywhere else in this API: the account-wide opt-out. Treating a per-list timestamp as an account-wide one would suppress that person across your whole integration because they left a single list, so read unsubscribed_scope if you need one field to branch on.
| list_id required | integer |
| query | string Search query for filtering contacts |
{- "data": [
- {
- "id": 0,
- "first_name": "string",
- "last_name": "string",
- "ip_address": "string",
- "unsubscribed_at": "2019-08-24T14:15:22Z",
- "created_at": "2019-08-24T14:15:22Z",
- "updated_at": "2019-08-24T14:15:22Z",
- "list_unsubscribed_at": "2019-08-24T14:15:22Z",
- "unsubscribed_scope": "list"
}
], - "current_page": 0,
- "total": 0,
- "per_page": 0
}Stops this one list for this one contact. Their other lists and their account-wide status are untouched, which is what distinguishes this from PATCH /unsubscribe — that endpoint is account-wide and is unchanged.
Idempotent. Repeating the request returns the original timestamp rather than moving it, so a retry never loses the date the person actually asked.
The opt-out is recorded durably, so it survives the contact being removed from the list and added back: re-adding them does not start sending again.
There is no matching resubscribe endpoint. Taking consent away on someone's behalf is safe for an integration to do; handing it back is not.
| list_id required | integer |
| email required | string <email> Email address of a contact already on this list |
{- "email": "[email protected]"
}{- "id": 0,
- "first_name": "string",
- "last_name": "string",
- "ip_address": "string",
- "unsubscribed_at": "2019-08-24T14:15:22Z",
- "created_at": "2019-08-24T14:15:22Z",
- "updated_at": "2019-08-24T14:15:22Z",
- "list_unsubscribed_at": "2019-08-24T14:15:22Z",
- "unsubscribed_scope": "list"
}Returns every contact on the list. This is membership, not subscription: someone who unsubscribed from the list is still a member and is still returned, so that an integration mirroring this endpoint can tell "removed from the list" apart from "asked to stop receiving it". Pass status to narrow it.
| list_id required | integer |
| query | string Search query for filtering contacts |
| status | string Enum: "subscribed" "unsubscribed" Optional. Omit for every member, which is the long-standing behaviour of this endpoint. |
{- "data": [
- {
- "id": 0,
- "first_name": "string",
- "last_name": "string",
- "ip_address": "string",
- "unsubscribed_at": "2019-08-24T14:15:22Z",
- "created_at": "2019-08-24T14:15:22Z",
- "updated_at": "2019-08-24T14:15:22Z"
}
], - "current_page": 0,
- "total": 0,
- "per_page": 0
}Adds an existing contact to a list. If the contact is already in the list, no duplicate is created.
| list_id required | integer |
| contact_id required | integer ID of the contact to add |
{- "contact_id": 0
}{- "id": 0,
- "first_name": "string",
- "last_name": "string",
- "ip_address": "string",
- "unsubscribed_at": "2019-08-24T14:15:22Z",
- "created_at": "2019-08-24T14:15:22Z",
- "updated_at": "2019-08-24T14:15:22Z"
}| list_id required | integer |
| contact_id required | integer |
{- "id": 0,
- "first_name": "string",
- "last_name": "string",
- "ip_address": "string",
- "unsubscribed_at": "2019-08-24T14:15:22Z",
- "created_at": "2019-08-24T14:15:22Z",
- "updated_at": "2019-08-24T14:15:22Z"
}| query | string Search query for filtering forms |
{- "data": [
- {
- "id": 0,
- "title": "string",
- "landing_page_id": 0,
- "redirect_url": "string",
- "gdpr_required": true,
- "url": "string",
- "lists": [
- {
- "id": 0,
- "name": "string",
- "user_id": 0,
- "subscribed_contacts_count": 0,
- "unsubscribed_contacts_count": 0,
- "public_description": "string",
- "average_email_open_percent": 0,
- "average_email_click_percent": 0,
- "created_at": "2019-08-24T14:15:22Z",
- "updated_at": "2019-08-24T14:15:22Z"
}
], - "created_at": "2019-08-24T14:15:22Z",
- "updated_at": "2019-08-24T14:15:22Z"
}
], - "current_page": 0,
- "total": 0,
- "per_page": 0
}Creates a subscription form linked to one or more lists. Free users are limited to 1 form.
| title required | string <= 191 characters |
| lists required | Array of integers Array of list IDs to attach |
| redirect_url | string or null <uri> URL to redirect to after subscription |
| gdpr_required | boolean Whether GDPR consent checkbox is required |
{- "title": "string",
- "lists": [
- 0
], - "gdpr_required": true
}{- "id": 0,
- "title": "string",
- "landing_page_id": 0,
- "redirect_url": "string",
- "gdpr_required": true,
- "url": "string",
- "lists": [
- {
- "id": 0,
- "name": "string",
- "user_id": 0,
- "subscribed_contacts_count": 0,
- "unsubscribed_contacts_count": 0,
- "public_description": "string",
- "average_email_open_percent": 0,
- "average_email_click_percent": 0,
- "created_at": "2019-08-24T14:15:22Z",
- "updated_at": "2019-08-24T14:15:22Z"
}
], - "created_at": "2019-08-24T14:15:22Z",
- "updated_at": "2019-08-24T14:15:22Z"
}{- "id": 0,
- "title": "string",
- "landing_page_id": 0,
- "redirect_url": "string",
- "gdpr_required": true,
- "url": "string",
- "lists": [
- {
- "id": 0,
- "name": "string",
- "user_id": 0,
- "subscribed_contacts_count": 0,
- "unsubscribed_contacts_count": 0,
- "public_description": "string",
- "average_email_open_percent": 0,
- "average_email_click_percent": 0,
- "created_at": "2019-08-24T14:15:22Z",
- "updated_at": "2019-08-24T14:15:22Z"
}
], - "created_at": "2019-08-24T14:15:22Z",
- "updated_at": "2019-08-24T14:15:22Z"
}| id required | integer |
| title | string <= 191 characters |
| lists | Array of integers Replaces all list assignments |
| redirect_url | string or null <uri> |
| gdpr_required | boolean |
{- "title": "string",
- "lists": [
- 0
], - "gdpr_required": true
}{- "id": 0,
- "title": "string",
- "landing_page_id": 0,
- "redirect_url": "string",
- "gdpr_required": true,
- "url": "string",
- "lists": [
- {
- "id": 0,
- "name": "string",
- "user_id": 0,
- "subscribed_contacts_count": 0,
- "unsubscribed_contacts_count": 0,
- "public_description": "string",
- "average_email_open_percent": 0,
- "average_email_click_percent": 0,
- "created_at": "2019-08-24T14:15:22Z",
- "updated_at": "2019-08-24T14:15:22Z"
}
], - "created_at": "2019-08-24T14:15:22Z",
- "updated_at": "2019-08-24T14:15:22Z"
}{- "data": [
- {
- "id": 0,
- "user_id": 0,
- "title": "string",
- "active": true,
- "created_at": "2019-08-24T14:15:22Z",
- "updated_at": "2019-08-24T14:15:22Z",
- "automation_triggers": [
- {
- "id": 0,
- "automation_id": 0,
- "type": "apply_list",
- "list_id": 0,
- "campaign_id": 0,
- "created_at": "2019-08-24T14:15:22Z",
- "updated_at": "2019-08-24T14:15:22Z"
}
], - "automation_items": [
- {
- "id": 0,
- "automation_id": 0,
- "campaign_id": 0,
- "delay_hours": 0,
- "send_order": 0,
- "campaign": {
- "id": 0,
- "title": "string",
- "subject": "string",
- "preview_text": "string",
- "html": "string",
- "from_name": "string",
- "scheduled_at": "2019-08-24T14:15:22Z",
- "sent_at": "2019-08-24T14:15:22Z",
- "timezone": "string",
- "is_web_gated": true,
- "created_at": "2019-08-24T14:15:22Z",
- "updated_at": "2019-08-24T14:15:22Z"
}, - "created_at": "2019-08-24T14:15:22Z",
- "updated_at": "2019-08-24T14:15:22Z"
}
]
}
], - "current_page": 0,
- "total": 0,
- "per_page": 0
}| title required | string |
| trigger_type | string Enum: "apply_list" "open_campaign" "click_campaign" |
| trigger_list_id | integer Required when trigger_type is apply_list |
| trigger_campaign_id | integer Required when trigger_type is open_campaign or click_campaign |
| active | boolean |
{- "title": "string",
- "trigger_type": "apply_list",
- "trigger_list_id": 0,
- "trigger_campaign_id": 0,
- "active": true
}{- "id": 0,
- "user_id": 0,
- "title": "string",
- "active": true,
- "created_at": "2019-08-24T14:15:22Z",
- "updated_at": "2019-08-24T14:15:22Z",
- "automation_triggers": [
- {
- "id": 0,
- "automation_id": 0,
- "type": "apply_list",
- "list_id": 0,
- "campaign_id": 0,
- "created_at": "2019-08-24T14:15:22Z",
- "updated_at": "2019-08-24T14:15:22Z"
}
], - "automation_items": [
- {
- "id": 0,
- "automation_id": 0,
- "campaign_id": 0,
- "delay_hours": 0,
- "send_order": 0,
- "campaign": {
- "id": 0,
- "title": "string",
- "subject": "string",
- "preview_text": "string",
- "html": "string",
- "from_name": "string",
- "scheduled_at": "2019-08-24T14:15:22Z",
- "sent_at": "2019-08-24T14:15:22Z",
- "timezone": "string",
- "is_web_gated": true,
- "created_at": "2019-08-24T14:15:22Z",
- "updated_at": "2019-08-24T14:15:22Z"
}, - "created_at": "2019-08-24T14:15:22Z",
- "updated_at": "2019-08-24T14:15:22Z"
}
]
}Returns automation with triggers, items, and campaign stats
| id required | integer |
{- "id": 0,
- "user_id": 0,
- "title": "string",
- "active": true,
- "created_at": "2019-08-24T14:15:22Z",
- "updated_at": "2019-08-24T14:15:22Z",
- "automation_triggers": [
- {
- "id": 0,
- "automation_id": 0,
- "type": "apply_list",
- "list_id": 0,
- "campaign_id": 0,
- "created_at": "2019-08-24T14:15:22Z",
- "updated_at": "2019-08-24T14:15:22Z"
}
], - "automation_items": [
- {
- "id": 0,
- "automation_id": 0,
- "campaign_id": 0,
- "delay_hours": 0,
- "send_order": 0,
- "campaign": {
- "id": 0,
- "title": "string",
- "subject": "string",
- "preview_text": "string",
- "html": "string",
- "from_name": "string",
- "scheduled_at": "2019-08-24T14:15:22Z",
- "sent_at": "2019-08-24T14:15:22Z",
- "timezone": "string",
- "is_web_gated": true,
- "created_at": "2019-08-24T14:15:22Z",
- "updated_at": "2019-08-24T14:15:22Z"
}, - "created_at": "2019-08-24T14:15:22Z",
- "updated_at": "2019-08-24T14:15:22Z"
}
]
}Update title, trigger, or active status. Activating reschedules stale deliverables.
| id required | integer |
| title | string |
| active | boolean |
| trigger_type | string Enum: "apply_list" "open_campaign" "click_campaign" |
| trigger_list_id | integer |
| trigger_campaign_id | integer |
{- "title": "string",
- "active": true,
- "trigger_type": "apply_list",
- "trigger_list_id": 0,
- "trigger_campaign_id": 0
}{- "id": 0,
- "user_id": 0,
- "title": "string",
- "active": true,
- "created_at": "2019-08-24T14:15:22Z",
- "updated_at": "2019-08-24T14:15:22Z",
- "automation_triggers": [
- {
- "id": 0,
- "automation_id": 0,
- "type": "apply_list",
- "list_id": 0,
- "campaign_id": 0,
- "created_at": "2019-08-24T14:15:22Z",
- "updated_at": "2019-08-24T14:15:22Z"
}
], - "automation_items": [
- {
- "id": 0,
- "automation_id": 0,
- "campaign_id": 0,
- "delay_hours": 0,
- "send_order": 0,
- "campaign": {
- "id": 0,
- "title": "string",
- "subject": "string",
- "preview_text": "string",
- "html": "string",
- "from_name": "string",
- "scheduled_at": "2019-08-24T14:15:22Z",
- "sent_at": "2019-08-24T14:15:22Z",
- "timezone": "string",
- "is_web_gated": true,
- "created_at": "2019-08-24T14:15:22Z",
- "updated_at": "2019-08-24T14:15:22Z"
}, - "created_at": "2019-08-24T14:15:22Z",
- "updated_at": "2019-08-24T14:15:22Z"
}
]
}| id required | integer |
| subject required | string |
| html required | string |
| from_name required | string |
| from_email required | string <email> |
| delay_hours | integer [ 0 .. 5000 ] Hours to wait before sending (default 24, first email defaults to 0) |
{- "subject": "string",
- "html": "string",
- "from_name": "string",
- "delay_hours": 5000
}{- "id": 0,
- "automation_id": 0,
- "campaign_id": 0,
- "delay_hours": 0,
- "send_order": 0,
- "campaign": {
- "id": 0,
- "title": "string",
- "subject": "string",
- "preview_text": "string",
- "html": "string",
- "from_name": "string",
- "scheduled_at": "2019-08-24T14:15:22Z",
- "sent_at": "2019-08-24T14:15:22Z",
- "timezone": "string",
- "is_web_gated": true,
- "created_at": "2019-08-24T14:15:22Z",
- "updated_at": "2019-08-24T14:15:22Z"
}, - "created_at": "2019-08-24T14:15:22Z",
- "updated_at": "2019-08-24T14:15:22Z"
}| id required | integer |
| subject | string |
| html | string |
| from_name | string |
| from_email | string <email> |
| delay_hours | integer [ 0 .. 5000 ] |
| send_order | integer >= 1 |
{- "subject": "string",
- "html": "string",
- "from_name": "string",
- "delay_hours": 5000,
- "send_order": 1
}{- "id": 0,
- "automation_id": 0,
- "campaign_id": 0,
- "delay_hours": 0,
- "send_order": 0,
- "campaign": {
- "id": 0,
- "title": "string",
- "subject": "string",
- "preview_text": "string",
- "html": "string",
- "from_name": "string",
- "scheduled_at": "2019-08-24T14:15:22Z",
- "sent_at": "2019-08-24T14:15:22Z",
- "timezone": "string",
- "is_web_gated": true,
- "created_at": "2019-08-24T14:15:22Z",
- "updated_at": "2019-08-24T14:15:22Z"
}, - "created_at": "2019-08-24T14:15:22Z",
- "updated_at": "2019-08-24T14:15:22Z"
}Returns a paginated list of the user's whitelabel/sender domains
{- "data": [
- {
- "id": 0,
- "domain": "string",
- "sendgrid_whitelabel_domain_id": 0,
- "validated_at": "2019-08-24T14:15:22Z",
- "dns": { },
- "created_at": "2019-08-24T14:15:22Z",
- "updated_at": "2019-08-24T14:15:22Z"
}
], - "current_page": 0,
- "total": 0,
- "per_page": 0
}Adds a new sender domain and creates the corresponding SendGrid whitelabel domain. Requires an active subscription and SendGrid subuser.
| domain required | string <= 191 characters Domain name (e.g., example.com). Do not include @ or protocol. |
{- "domain": "string"
}{- "id": 0,
- "domain": "string",
- "sendgrid_whitelabel_domain_id": 0,
- "validated_at": "2019-08-24T14:15:22Z",
- "dns": { },
- "created_at": "2019-08-24T14:15:22Z",
- "updated_at": "2019-08-24T14:15:22Z"
}Returns domain details including DNS records needed for verification
| id required | integer |
{- "id": 0,
- "domain": "string",
- "sendgrid_whitelabel_domain_id": 0,
- "validated_at": "2019-08-24T14:15:22Z",
- "dns": { },
- "created_at": "2019-08-24T14:15:22Z",
- "updated_at": "2019-08-24T14:15:22Z"
}Triggers DNS validation for the domain via SendGrid. Returns whether validation passed and any errors for specific DNS records.
| id required | integer |
{- "message": "string",
- "valid": true,
- "domain": {
- "id": 0,
- "domain": "string",
- "sendgrid_whitelabel_domain_id": 0,
- "validated_at": "2019-08-24T14:15:22Z",
- "dns": { },
- "created_at": "2019-08-24T14:15:22Z",
- "updated_at": "2019-08-24T14:15:22Z"
}
}{- "id": 0,
- "name": "string",
- "contacts_count": 0,
- "contact_limit": 0,
- "created_at": "2019-08-24T14:15:22Z",
- "updated_at": "2019-08-24T14:15:22Z"
}Creates a custom field for contacts. The name is auto-generated from the label as a slug.
Provide type (text, number, or date) or the existing contact_field_type_id UUID.
If both are provided, they must identify the same type. UUIDs are the same for every account:
| Type | contact_field_type_id |
|---|---|
| text | 0abe5601-7738-43c8-858f-81911ecf89ee |
| number | e43c6471-b3ff-4541-80c5-964c62decd15 |
| date | fe7dd76c-5113-4159-bc7c-0dbf0ed8d404 |
The value is immutable after creation and is not returned by any endpoint - responses
carry the human-readable type (text, number, date) instead.
| label required | string <= 191 characters Human-readable field label |
| contact_field_type_id | string Enum: "0abe5601-7738-43c8-858f-81911ecf89ee" "e43c6471-b3ff-4541-80c5-964c62decd15" "fe7dd76c-5113-4159-bc7c-0dbf0ed8d404" UUID of the field's data type. Required unless type is provided; must match type if both are provided. |
| type required | string Enum: "text" "number" "date" Readable data type; required unless contact_field_type_id is provided. |
{- "label": "string",
- "contact_field_type_id": "0abe5601-7738-43c8-858f-81911ecf89ee",
- "type": "text"
}{- "id": 0,
- "label": "string",
- "name": "string",
- "type": "text"
}Updates the label and auto-regenerates the name slug.
Note: contact_field_type_id is immutable and cannot be changed after creation.
| id required | integer |
| label required | string <= 191 characters |
{- "label": "string"
}{- "id": 0,
- "label": "string",
- "name": "string",
- "type": "text"
}