SendFox API (1.5.0)

Download OpenAPI specification:Download

Introduction

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.

Authentication

Personal Access Token

Create a personal access token at https://sendfox.com/account/oauth. Once created, use it in the Authorization header:

Authorization: Bearer {TOKEN}

OAuth 2.0 Client

For integrations that require user authentication, create an OAuth 2.0 client at https://sendfox.com/account/oauth

Rate Limits

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 minute
  • X-RateLimit-Remaining: Remaining requests in current window
  • Retry-After: Seconds until rate limit resets (only on 429 responses)

Error Responses

All error responses use standard HTTP status codes and Laravel's default error format:

  • message: Human-readable error description
  • errors: Field-level validation errors (on 422 responses)

Plans & API Access

Every plan can create an API key. Lifetime and Pro plans get the whole API. The Free plan gets a limited set of endpoints: GET /me; reading lists, tags, contact tags, contact fields, campaigns and campaign stats, saved templates and sections, automations, and forms; creating contacts, adding a contact to a list, and tagging a contact; transactional email; and, on accounts with transactional access, sending domains. Any other endpoint returns 402 Payment Required for a Free account. A key's access follows the account's plan, so upgrading opens the rest without a new key.

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.

Transactional

Send email from your app, one per request or up to 100 per batch: login codes, receipts, notifications. Unlike the rest of the API, transactional routes are available on every plan, including free. Use any API key from Settings > API tokens (the Transactional page makes your first one); it works on every route your plan includes (see Plans & API Access), the Domains endpoints included, so your app can add, verify and remove the domains it sends from. Transactional keys made before keys were unified carry transactional:* scopes and keep working on these routes and /domains only; they are rejected by the rest of the API.

Transactional is for messages your users trigger: password resets, receipts, notifications about their own account. Newsletters, promotions, re-engagement and review requests belong in SendFox campaigns.

Switching from Resend

Keep your code. Resend's SDKs read a base URL from the environment and SendFox accepts Resend's request shape at /transactional/resend, so the switch is two variables plus a verified domain:

RESEND_BASE_URL=https://api.sendfox.com/transactional/resend
RESEND_API_KEY=<your SendFox transactional key>

resend.emails.send(), resend.batch.send() and resend.emails.get() then go to POST /transactional/resend/emails, POST /transactional/resend/emails/batch and GET /transactional/resend/emails/{id}: to, cc and bcc may be a string or an array, reply_to may be a one-item array, and tags: [{name, value}] becomes the SendFox tag (the first value). Every recipient counts as one send against your plan, as Resend counts them, and a message may carry up to 10 addresses (Resend allows 50). Responses and errors are SendFox's (202 {id, status}, and the error catalog below with name, message and fix). attachments work as on Resend ({filename, content, content_type}, with content in base64 or a byte array), except path URLs and inline images (content_id), which are refused. scheduled_at is not supported yet. Anything unsupported is refused with a 422 validation_error rather than sent without it. For webhooks, set your endpoint to the Resend format (see Webhooks below) and your existing handler and signature check keep working with the SendFox secret.

Error catalog

Every non-2xx response has the same shape: status, name, message, fix, link, request_id.

Status name When
401 missing_api_key No Authorization header
401 invalid_api_key The key is wrong or revoked
403 insufficient_scope A free-plan key without the needed transactional scope
403 feature_not_available Transactional email is not enabled on the account
403 domain_not_verified The from domain is not verified for this account
403 sandbox_recipient_not_allowed Sandbox sender to an address that is not yours
403 recipient_suppressed A recipient is on your suppression list (suppressed lists them)
403 account_paused Transactional sending is paused for review
403 account_restricted The SendFox account is restricted
404 not_found No message with that id on this account
409 invalid_idempotent_request Same Idempotency-Key, different body, inside 24 hours
422 validation_error Field errors in errors
429 rate_limit_exceeded Over 5/s or 300/min; honor Retry-After
429 monthly_quota_exceeded The plan's sends are used up (remaining, requested); upgrade the SendFox plan from Account > Billing

Webhooks

Add one https URL under Transactional > Webhooks and SendFox POSTs a JSON event to it for each of sent, delivered, delayed, bounced, complained and failed that you pick. Sandbox test addresses send events too. The URL must resolve to a public address; redirects are not followed, and SendFox waits up to 10 seconds for an answer.

{
  "id": "whd_5f0c2a9e1b7d4c3a8e6f1d2b",
  "event": "delivered",
  "occurred_at": "2026-10-10T14:03:22+00:00",
  "data": {
    "message": { "id": "tm_…", "status": "delivered", "to": ["[email protected]"], "subject": "Your code", "tag": "login_code", "…": "same fields as GET /transactional/emails/{id}, without events" },
    "recipient": "[email protected]",
    "status": null,
    "reason": null
  }
}

recipient is the address the event is about (a message with cc gets one event per recipient). status and reason carry the receiving server's answer on bounced, delayed and failed.

Any 2xx answer counts as delivered. Anything else, or no answer, is retried after 1 minute, 15 minutes, 2 hours and 18 hours, then given up. Retries send the same body with the same id, so dedupe on it.

Verify every request. The X-SendFox-Signature header is t={unix seconds},v1={hex}, where hex is HMAC-SHA256 of {t}.{raw body} keyed with your signing secret (the whole whsec_… string). Compute it over the raw body before parsing the JSON, compare in constant time, and reject a t more than five minutes old. Requests also carry X-SendFox-Event and X-SendFox-Delivery (the body's id).

Resend format. Switching from Resend? Set the webhook's format to Resend under Transactional > Webhooks and the body is shaped like a Resend event instead, with the same event types (email.sent, email.delivered, email.delivery_delayed, email.bounced, email.complained, email.failed) and svix-id, svix-timestamp and svix-signature headers signed the way Svix signs, so your existing handler and webhooks.verify() call keep working with the SendFox secret. data.email_id is the SendFox tm_ id, data.tags is {"tag": "<your tag>"}, a bounce or delay carries Resend's bounce object with the receiving server's reason, and data.recipient names the address the event is about.

{
  "type": "email.bounced",
  "created_at": "2026-10-10T14:03:22+00:00",
  "data": {
    "created_at": "2026-10-10T14:03:20+00:00",
    "email_id": "tm_…",
    "from": "Acme <[email protected]>",
    "to": ["[email protected]"],
    "subject": "Your code",
    "tags": { "tag": "login_code" },
    "bounce": { "message": "550 5.1.1 User unknown", "subType": "5.1.1", "type": "Permanent" },
    "recipient": "[email protected]"
  }
}

Send an email

Queues one email. The response is immediate; delivery happens in the background and is reported through GET /transactional/emails/{id} and your webhook.

Idempotency. Send an Idempotency-Key header (1 to 256 characters) to make retries safe. Within 24 hours, the same key with the same body returns the original message (with an idempotent-replayed: true header); the same key with a different body is a 409 invalid_idempotent_request.

Sandbox. Send from [email protected] with no DNS setup. It delivers only to your own account email and to the four test addresses. Test addresses never reach a mailbox and never count toward your limits; each one synthesizes an outcome so you can exercise your integration: [email protected], [email protected], [email protected], [email protected].

Limits. Transactional email has no quota of its own: every address in to, cc and bcc counts as one send against your SendFox plan, the same monthly sends your campaigns use, with no daily cap. When the plan has a cap, every response carries x-sendfox-sends-remaining, the sends left across campaigns and transactional email; a plan without an enforced cap sends no header. Past the cap, sends are refused with 429 monthly_quota_exceeded carrying remaining and requested. Each message holds at most 10 addresses across to, cc and bcc, at most 2 in bcc. Requests are limited to 5 per second and 300 per minute per key.

Custom headers. Pass headers to add list-management and threading headers. For one-click unsubscribe (RFC 8058, which Gmail and Yahoo require of bulk senders) send List-Unsubscribe with an https: URL in angle brackets and List-Unsubscribe-Post set to List-Unsubscribe=One-Click; the URL must accept a POST whose body is List-Unsubscribe=One-Click and unsubscribe the recipient without any further click. See the headers field for what is allowed.

Authorizations:
oauth2
header Parameters
Idempotency-Key
string [ 1 .. 256 ] characters
Request Body schema: application/json
from
required
string <= 320 characters

Sender. Either an address or a display name followed by one in angle brackets. The domain must be plain ASCII (for an internationalized domain, use its punycode form, starting xn--) and verified under Account > Domains (a subdomain of a verified domain also works), or be the sandbox sender [email protected], which needs no DNS but only delivers to your own account email and the test addresses (sandbox mail always shows the SendFox Onboarding display name).

to
required
Array of strings <email> [ 1 .. 10 ] items

1 to 10 addresses. to, cc and bcc together may hold at most 10, and every address counts as one email against your plan's sends, so a message to three people uses three.

cc
Array of strings <email> <= 10 items
bcc
Array of strings <email> <= 2 items

At most 2 addresses; counts toward the 10-address total.

reply_to
string or null <email>
subject
required
string <= 998 characters
html
string

HTML body, at most 500 KB (UTF-8 bytes). Send html, text, or both.

text
string

Plain-text body, at most 100 KB (UTF-8 bytes). Send html, text, or both.

tag
string or null <= 64 characters ^[a-z0-9][a-z0-9_.-]*$

A label for grouping in the dashboard, for per-tag open/click tracking switches, and for per-tag alerts. Lowercase letters, digits, dots, dashes and underscores. Messages without a tag are grouped by subject. Tags that look like authentication mail (login_code, password_reset, otp, ...) can never have tracking turned on. An account may hold at most 200 distinct tags, so tag by kind of email (receipt), not per customer or order.

object or null <= 20 properties

Custom headers, at most 20, as {"Name": "value"}. Allowed: List-Unsubscribe, List-Unsubscribe-Post, List-Id, X-Entity-Ref-ID, References, In-Reply-To, and any other X-* header except X-SendFox-* and X-SG-*. Headers SendFox or SendGrid set (From, To, Cc, Bcc, Reply-To, Subject, Content-*, MIME-Version, Date, Message-ID, Return-Path, DKIM-Signature, Received) are refused with a 422. Names may hold letters, digits and dashes; values may hold up to 998 characters and no line breaks. List-Unsubscribe must be one or more mailto: or https: URLs in angle brackets, and List-Unsubscribe-Post must be exactly List-Unsubscribe=One-Click with an https: URL in List-Unsubscribe, as RFC 8058 requires.

Array of objects or null <= 10 items

Files to attach, at most 10 and 5 MB in total once decoded. Each is {filename, content, content_type?} with content the file in base64 (line breaks are fine). content_type is guessed from the extension when omitted and may carry parameters, like text/calendar; method=REQUEST for a calendar invite. Executable and script files (.exe, .js, .bat, .scr, ...) are refused, as are URLs (path): send the file itself. Not accepted on the batch endpoint. The file is kept only until the email is sent or fails; GET /transactional/emails/{id} shows its name, type and size.

Responses

Request samples

Content type
application/json
{
  • "from": "Acme <[email protected]>",
  • "bcc": [],
  • "reply_to": "[email protected]",
  • "subject": "Your login code",
  • "html": "<p>Your code is <strong>483 920</strong>.</p>",
  • "text": "string",
  • "tag": "login_code",
  • "headers": {
    },
  • "attachments": [
    ]
}

Response samples

Content type
application/json
{
  • "id": "tm_4f0c9b2e7d1a5c3e8b6f2a9d",
  • "status": "queued"
}

List emails

Newest first. Standard SendFox paginator; bodies are never included.

Authorizations:
oauth2
query Parameters
status
string
Enum: "queued" "sent" "delivered" "bounced" "complained" "failed" "suppressed"
tag
string
to
string <email>

Exact match on the first to address.

mode
string
Enum: "live" "test"
page
integer
per_page
integer <= 100
Default: 25

Responses

Response samples

Content type
application/json
{
  • "data": [
    ],
  • "current_page": 0,
  • "total": 0,
  • "per_page": 0
}

Send a batch of emails

Queues up to 100 emails in one request and reports a result per item. Items are independent: one invalid address, unverified sender or suppressed recipient is reported at its index and the rest are queued. The response is 200 whenever the batch itself was accepted, even if every item was refused; read each entry's error.

Limits. The whole batch's sends (one per to, cc and bcc address across every item) are reserved against your plan before any item is looked at. A batch that does not fit is refused with 429 monthly_quota_exceeded and nothing is sent, so a retry can never half-send. Sends for refused items, test addresses and replays are returned. A batch request counts as one request against the 5 per second and 300 per minute rate limit; the plan's sends are what bound volume.

Idempotency. Two levels. An Idempotency-Key header makes the whole batch replayable for 24 hours: the same key with the same emails returns the original results with idempotent-replayed: true; a different body is a 409 invalid_idempotent_request. A per-item idempotency_key protects each email on its own, exactly like the header on POST /transactional/emails, and is the one to rely on when your retry may be built from a different batch.

Sandbox, test addresses and tags work per item as on the single route.

Authorizations:
oauth2
header Parameters
Idempotency-Key
string [ 1 .. 256 ] characters
Request Body schema: application/json
required
Array of objects [ 1 .. 100 ] items

1 to 100 emails. Each item takes the same fields as POST /transactional/emails plus an optional idempotency_key. The html and text of all items together may total at most 5 MB.

Responses

Request samples

Content type
application/json
{
  • "emails": [
    ]
}

Response samples

Content type
application/json
{
  • "data": [
    ]
}

Get an email

The message without its bodies, plus its event timeline.

Authorizations:
oauth2
path Parameters
id
required
string
Example: tm_4f0c9b2e7d1a5c3e8b6f2a9d

Responses

Response samples

Content type
application/json
{
  • "id": "tm_4f0c9b2e7d1a5c3e8b6f2a9d",
  • "status": "queued",
  • "mode": "live",
  • "from": "string",
  • "to": [
    ],
  • "cc": [
    ],
  • "bcc": [
    ],
  • "reply_to": "string",
  • "subject": "string",
  • "tag": "string",
  • "headers": {
    },
  • "attachments": [
    ],
  • "idempotency_key": "string",
  • "failure": {
    },
  • "accepted_at": "2019-08-24T14:15:22Z",
  • "sent_at": "2019-08-24T14:15:22Z",
  • "delivered_at": "2019-08-24T14:15:22Z",
  • "failed_at": "2019-08-24T14:15:22Z",
  • "created_at": "2019-08-24T14:15:22Z",
  • "events": [
    ]
}

Contacts

List contacts

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.

Authorizations:
oauth2
query Parameters
query
string

Search query for filtering contacts

unsubscribed
boolean

Filter unsubscribed contacts

email
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

Responses

Response samples

Content type
application/json
{
  • "count": 0,
  • "filter": "string"
}

Create a new contact

Authorizations:
oauth2
Request Body schema: application/json
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

Responses

Request samples

Content type
application/json
{
  • "email": "[email protected]",
  • "first_name": "string",
  • "last_name": "string",
  • "ip_address": "string",
  • "lists": [
    ],
  • "contact_fields": [
    ]
}

Response samples

Content type
application/json
{
  • "id": 0,
  • "email": "[email protected]",
  • "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"
}

Get a specific contact

Authorizations:
oauth2
path Parameters
id
required
integer

Responses

Response samples

Content type
application/json
{
  • "id": 0,
  • "email": "[email protected]",
  • "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 a contact

Update contact details including name, list memberships, and custom fields

Authorizations:
oauth2
path Parameters
id
required
integer
Request Body schema: application/json
first_name
string
last_name
string
lists
Array of integers

Array of list IDs (replaces all current list memberships)

Array of objects

Responses

Request samples

Content type
application/json
{
  • "first_name": "string",
  • "last_name": "string",
  • "lists": [
    ],
  • "contact_fields": [
    ]
}

Response samples

Content type
application/json
{
  • "id": 0,
  • "email": "[email protected]",
  • "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"
}

Delete a contact

Soft-deletes a contact and cancels any scheduled deliverables

Authorizations:
oauth2
path Parameters
id
required
integer

Responses

Response samples

Content type
application/json
{
  • "message": "string"
}

Get email activity for a contact

Returns paginated email deliverables and contact-level engagement summary

Authorizations:
oauth2
path Parameters
id
required
integer

Responses

Response samples

Content type
application/json
{
  • "contact": {
    },
  • "deliverables": {
    }
}

Batch import contacts

Import up to 1,000 contacts in a single request. Creates new contacts or updates existing ones.

Authorizations:
oauth2
Request Body schema: application/json
required
Array of objects <= 1000 items

Responses

Request samples

Content type
application/json
{
  • "contacts": []
}

Response samples

Content type
application/json
{
  • "created": 0,
  • "updated": 0
}

Apply a tag or list change to every contact matching a filter

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.

Authorizations:
oauth2
Request Body schema: application/json
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".

Responses

Request samples

Content type
application/json
{
  • "action": "apply_tag",
  • "target_id": 0,
  • "dry_run": false,
  • "filter": {
    }
}

Response samples

Content type
application/json
{
  • "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"
}

Check the progress of a bulk contact action

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.

Authorizations:
oauth2
path Parameters
id
required
integer

Responses

Response samples

Content type
application/json
{
  • "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"
}

List unsubscribed contacts

Authorizations:
oauth2
query Parameters
query
string

Search query for filtering contacts

Responses

Response samples

Content type
application/json
{
  • "data": [
    ],
  • "current_page": 0,
  • "total": 0,
  • "per_page": 0
}

Unsubscribe a contact by email

Authorizations:
oauth2
Request Body schema: application/json
email
required
string <email>

Responses

Request samples

Content type
application/json
{}

Response samples

Content type
application/json
{
  • "id": 0,
  • "email": "[email protected]",
  • "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"
}

Campaigns

List campaigns

Returns a paginated list of campaigns (100 per page)

Authorizations:
oauth2

Responses

Response samples

Content type
application/json
{
  • "data": [
    ],
  • "current_page": 0,
  • "total": 0,
  • "per_page": 0
}

Create a new campaign

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.

Authorizations:
oauth2
Request Body schema: application/json
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. Unless the campaign uses the Code Your Own layout, tags outside the editor's set are removed on save, along with event-handler attributes (such as onclick) and javascript: links.

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. The account-wide signup requirement in Settings overrides this choice when enabled, including when false is sent or this field is omitted.

Responses

Request samples

Content type
application/json
{
  • "title": "string",
  • "subject": "string",
  • "preview_text": "string",
  • "html": "string",
  • "from_name": "string",
  • "from_email": "[email protected]",
  • "scheduled_at": "2019-08-24T14:15:22Z",
  • "lists": [
    ],
  • "excluded_lists": [
    ],
  • "to_contact_tags": [
    ],
  • "excluded_contact_tags": [
    ],
  • "web_publish": true,
  • "web_gate": false
}

Response samples

Content type
application/json
{
  • "id": 0,
  • "title": "string",
  • "subject": "string",
  • "preview_text": "string",
  • "html": "string",
  • "from_name": "string",
  • "from_email": "[email protected]",
  • "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"
}

Get statistics for multiple campaigns

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.

Authorizations:
oauth2
query Parameters
campaign_ids[]
required
Array of integers [ 1 .. 100 ] items unique [ items >= 1 ]

Repeat campaign_ids[] for each campaign ID.

Responses

Response samples

Content type
application/json
{
  • "data": [
    ]
}

Get a specific campaign

Authorizations:
oauth2
path Parameters
id
required
integer

Responses

Response samples

Content type
application/json
{
  • "id": 0,
  • "title": "string",
  • "subject": "string",
  • "preview_text": "string",
  • "html": "string",
  • "from_name": "string",
  • "from_email": "[email protected]",
  • "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"
}

Update a draft campaign

Only draft campaigns (not yet sent) can be updated. All fields are optional.

Authorizations:
oauth2
path Parameters
id
required
integer
Request Body schema: application/json
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

Email body HTML content. Filtered on save the same way as when creating a campaign.

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).

Responses

Request samples

Content type
application/json
{
  • "title": "string",
  • "subject": "string",
  • "preview_text": "string",
  • "html": "string",
  • "from_name": "string",
  • "from_email": "[email protected]",
  • "scheduled_at": "2019-08-24T14:15:22Z",
  • "lists": [
    ],
  • "excluded_lists": [
    ],
  • "to_contact_tags": [
    ],
  • "excluded_contact_tags": [
    ]
}

Response samples

Content type
application/json
{
  • "id": 0,
  • "title": "string",
  • "subject": "string",
  • "preview_text": "string",
  • "html": "string",
  • "from_name": "string",
  • "from_email": "[email protected]",
  • "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"
}

Delete a draft campaign

Only draft campaigns (not yet sent) can be deleted. Uses soft delete.

Authorizations:
oauth2
path Parameters
id
required
integer

Responses

Response samples

Content type
application/json
{
  • "message": "string"
}

Send a campaign immediately

Schedules a draft campaign for immediate sending. The campaign must:

  • Not already be sent or scheduled
  • Have at least one list assigned
  • Not be a confirmation (double opt-in) email — those are delivered to each contact as they subscribe, never as a broadcast
  • The user must not be in a warmup/throttle period

All existing abuse prevention applies automatically: content approval workflow, sending throttles, spam detection, and bounce rate monitoring.

Authorizations:
oauth2
path Parameters
id
required
integer

Responses

Response samples

Content type
application/json
{
  • "id": 0,
  • "title": "string",
  • "subject": "string",
  • "preview_text": "string",
  • "html": "string",
  • "from_name": "string",
  • "from_email": "[email protected]",
  • "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"
}

Send a test of a campaign

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.

Authorizations:
oauth2
path Parameters
id
required
integer
Request Body schema: application/json
emails
required
Array of strings <email> [ 1 .. 30 ] items

Addresses to deliver the test to. Trimmed and de-duplicated.

Responses

Request samples

Content type
application/json
{}

Response samples

Content type
application/json
{}

Get campaign performance statistics

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.

Authorizations:
oauth2
path Parameters
id
required
integer
query Parameters
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.

Responses

Response samples

Content type
application/json
{
  • "link_stats": [
    ],
  • "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
}

List the contacts behind a campaign's engagement

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.

Authorizations:
oauth2
path Parameters
id
required
integer
query Parameters
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

Responses

Response samples

Content type
application/json
{
  • "data": [
    ],
  • "current_page": 0,
  • "total": 0,
  • "per_page": 0
}

Clone a sent campaign at part of its original audience

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.

Authorizations:
oauth2
path Parameters
id
required
integer
Request Body schema: application/json
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

Responses

Request samples

Content type
application/json
{
  • "audience": "non_openers",
  • "subject": "string",
  • "title": "string",
  • "scheduled_at": "2019-08-24T14:15:22Z"
}

Response samples

Content type
application/json
{
  • "id": 0,
  • "title": "string",
  • "subject": "string",
  • "preview_text": "string",
  • "html": "string",
  • "from_name": "string",
  • "from_email": "[email protected]",
  • "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"
}

Lists

List contact lists

Authorizations:
oauth2
query Parameters
query
string

Search query for filtering lists

Responses

Response samples

Content type
application/json
{
  • "data": [
    ],
  • "current_page": 0,
  • "total": 0,
  • "per_page": 0
}

Create a new contact list

Authorizations:
oauth2
Request Body schema: application/json
name
required
string

Responses

Request samples

Content type
application/json
{
  • "name": "string"
}

Response samples

Content type
application/json
{
  • "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"
}

Get a specific contact list

Returns list details including average open and click rates

Authorizations:
oauth2
path Parameters
id
required
integer

Responses

Response samples

Content type
application/json
{
  • "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"
}

Update a contact list

Authorizations:
oauth2
path Parameters
id
required
integer
Request Body schema: application/json
name
required
string <= 191 characters

Responses

Request samples

Content type
application/json
{
  • "name": "string"
}

Response samples

Content type
application/json
{
  • "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"
}

Delete a contact list

Soft-deletes a list. Returns 409 if the list is used by forms, landing pages, or automations.

Authorizations:
oauth2
path Parameters
id
required
integer

Responses

Response samples

Content type
application/json
{
  • "message": "string"
}

Get everyone who has stopped this list

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.

Authorizations:
oauth2
path Parameters
list_id
required
integer
query Parameters
query
string

Search query for filtering contacts

Responses

Response samples

Content type
application/json
{
  • "data": [
    ],
  • "current_page": 0,
  • "total": 0,
  • "per_page": 0
}

Unsubscribe a contact from one list

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.

Authorizations:
oauth2
path Parameters
list_id
required
integer
Request Body schema: application/json
email
required
string <email>

Email address of a contact already on this list

Responses

Request samples

Content type
application/json
{}

Response samples

Content type
application/json
{
  • "id": 0,
  • "email": "[email protected]",
  • "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"
}

Get contacts in a 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.

Authorizations:
oauth2
path Parameters
list_id
required
integer
query Parameters
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. subscribed returns members who can still be mailed from this list; unsubscribed returns members who have stopped it, whether by leaving this list or by unsubscribing from the account. The two do not sum to the unfiltered total, because an address flagged invalid is in neither. A hard bounce counts as unsubscribed, since bouncing sets the account-wide flag. They match the Subscribed and Unsubscribed figures shown on the Lists page exactly. An unrecognised value is rejected with 400 rather than ignored.

Responses

Response samples

Content type
application/json
{
  • "data": [
    ],
  • "current_page": 0,
  • "total": 0,
  • "per_page": 0
}

Add a contact to a list

Adds an existing contact to a list. If the contact is already in the list, no duplicate is created.

Authorizations:
oauth2
path Parameters
list_id
required
integer
Request Body schema: application/json
contact_id
required
integer

ID of the contact to add

Responses

Request samples

Content type
application/json
{
  • "contact_id": 0
}

Response samples

Content type
application/json
{
  • "id": 0,
  • "email": "[email protected]",
  • "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"
}

Remove a contact from a list

Authorizations:
oauth2
path Parameters
list_id
required
integer
contact_id
required
integer

Responses

Response samples

Content type
application/json
{
  • "id": 0,
  • "email": "[email protected]",
  • "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"
}

Forms

List forms

Authorizations:
oauth2
query Parameters
query
string

Search query for filtering forms

Responses

Response samples

Content type
application/json
{
  • "data": [
    ],
  • "current_page": 0,
  • "total": 0,
  • "per_page": 0
}

Create a new form

Creates a subscription form linked to one or more lists. Free users are limited to 1 form.

Authorizations:
oauth2
Request Body schema: application/json
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

Responses

Request samples

Content type
application/json
{}

Response samples

Content type
application/json
{
  • "id": 0,
  • "title": "string",
  • "landing_page_id": 0,
  • "redirect_url": "string",
  • "gdpr_required": true,
  • "url": "string",
  • "lists": [
    ],
  • "created_at": "2019-08-24T14:15:22Z",
  • "updated_at": "2019-08-24T14:15:22Z"
}

Get a specific form

Authorizations:
oauth2
path Parameters
id
required
integer

Responses

Response samples

Content type
application/json
{
  • "id": 0,
  • "title": "string",
  • "landing_page_id": 0,
  • "redirect_url": "string",
  • "gdpr_required": true,
  • "url": "string",
  • "lists": [
    ],
  • "created_at": "2019-08-24T14:15:22Z",
  • "updated_at": "2019-08-24T14:15:22Z"
}

Update a form

Authorizations:
oauth2
path Parameters
id
required
integer
Request Body schema: application/json
title
string <= 191 characters
lists
Array of integers

Replaces all list assignments

redirect_url
string or null <uri>
gdpr_required
boolean

Responses

Request samples

Content type
application/json
{}

Response samples

Content type
application/json
{
  • "id": 0,
  • "title": "string",
  • "landing_page_id": 0,
  • "redirect_url": "string",
  • "gdpr_required": true,
  • "url": "string",
  • "lists": [
    ],
  • "created_at": "2019-08-24T14:15:22Z",
  • "updated_at": "2019-08-24T14:15:22Z"
}

Delete a form

Soft-deletes the form

Authorizations:
oauth2
path Parameters
id
required
integer

Responses

Response samples

Content type
application/json
{
  • "message": "string"
}

Automations

List automations

Authorizations:
oauth2

Responses

Response samples

Content type
application/json
{
  • "data": [
    ],
  • "current_page": 0,
  • "total": 0,
  • "per_page": 0
}

Create an automation

Authorizations:
oauth2
Request Body schema: application/json
title
required
string
trigger_type
string
Enum: "apply_list" "apply_tag" "open_campaign" "click_campaign"
trigger_list_id
integer

Required when trigger_type is apply_list

trigger_contact_tag_id
integer

Required when trigger_type is apply_tag

trigger_campaign_id
integer

Required when trigger_type is open_campaign or click_campaign. Any campaign you own, sent or not yet sent, including an email inside another automation.

active
boolean

Responses

Request samples

Content type
application/json
{
  • "title": "string",
  • "trigger_type": "apply_list",
  • "trigger_list_id": 0,
  • "trigger_contact_tag_id": 0,
  • "trigger_campaign_id": 0,
  • "active": true
}

Response samples

Content type
application/json
{
  • "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": [
    ],
  • "automation_items": [
    ]
}

Get a specific automation

Returns automation with triggers, items, and campaign stats

Authorizations:
oauth2
path Parameters
id
required
integer

Responses

Response samples

Content type
application/json
{
  • "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": [
    ],
  • "automation_items": [
    ]
}

Update an automation

Update title, trigger, or active status. Activating reschedules stale deliverables.

Authorizations:
oauth2
path Parameters
id
required
integer
Request Body schema: application/json
title
string
active
boolean
trigger_type
string
Enum: "apply_list" "apply_tag" "open_campaign" "click_campaign"
trigger_list_id
integer

Required when trigger_type is apply_list

trigger_contact_tag_id
integer

Required when trigger_type is apply_tag

trigger_campaign_id
integer

Required when trigger_type is open_campaign or click_campaign

Responses

Request samples

Content type
application/json
{
  • "title": "string",
  • "active": true,
  • "trigger_type": "apply_list",
  • "trigger_list_id": 0,
  • "trigger_contact_tag_id": 0,
  • "trigger_campaign_id": 0
}

Response samples

Content type
application/json
{
  • "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": [
    ],
  • "automation_items": [
    ]
}

Delete an automation

Soft-deletes the automation and cancels all scheduled deliverables

Authorizations:
oauth2
path Parameters
id
required
integer

Responses

Response samples

Content type
application/json
{
  • "message": "string"
}

Add an email to an automation

Authorizations:
oauth2
path Parameters
id
required
integer
Request Body schema: application/json
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)

Responses

Request samples

Content type
application/json
{
  • "subject": "string",
  • "html": "string",
  • "from_name": "string",
  • "from_email": "[email protected]",
  • "delay_hours": 5000
}

Response samples

Content type
application/json
{
  • "id": 0,
  • "type": "email",
  • "automation_id": 0,
  • "campaign_id": 0,
  • "parent_item_id": 0,
  • "branch": "yes",
  • "delay_hours": 0,
  • "send_order": 0,
  • "condition_type": "opened_previous",
  • "condition_list_id": 0,
  • "condition_contact_tag_id": 0,
  • "condition_email_link_id": 0,
  • "yes_path_ends": true,
  • "no_path_ends": true,
  • "campaign": {
    },
  • "created_at": "2019-08-24T14:15:22Z",
  • "updated_at": "2019-08-24T14:15:22Z"
}

Update an automation email

Authorizations:
oauth2
path Parameters
id
required
integer
Request Body schema: application/json
subject
string
html
string
from_name
string
from_email
string <email>
delay_hours
integer [ 0 .. 5000 ]
send_order
integer >= 1

Responses

Request samples

Content type
application/json
{
  • "subject": "string",
  • "html": "string",
  • "from_name": "string",
  • "from_email": "[email protected]",
  • "delay_hours": 5000,
  • "send_order": 1
}

Response samples

Content type
application/json
{
  • "id": 0,
  • "type": "email",
  • "automation_id": 0,
  • "campaign_id": 0,
  • "parent_item_id": 0,
  • "branch": "yes",
  • "delay_hours": 0,
  • "send_order": 0,
  • "condition_type": "opened_previous",
  • "condition_list_id": 0,
  • "condition_contact_tag_id": 0,
  • "condition_email_link_id": 0,
  • "yes_path_ends": true,
  • "no_path_ends": true,
  • "campaign": {
    },
  • "created_at": "2019-08-24T14:15:22Z",
  • "updated_at": "2019-08-24T14:15:22Z"
}

Remove an email from an automation

Authorizations:
oauth2
path Parameters
id
required
integer

Responses

Response samples

Content type
application/json
{
  • "message": "string"
}

Domains

List sender domains

Returns a paginated list of the user's whitelabel/sender domains.

Sending domains are shared by marketing campaigns and transactional email. Every Domains endpoint accepts a paid-plan API key or a transactional key that carries the transactional:domains scope. A transactional key without that scope gets a 403 in the transactional error shape (insufficient_scope), and one on an account without transactional access gets feature_not_available.

Authorizations:
oauth2

Responses

Response samples

Content type
application/json
{
  • "data": [
    ],
  • "current_page": 0,
  • "total": 0,
  • "per_page": 0
}

Add a sender domain

Adds a new sender domain and creates the corresponding SendGrid whitelabel domain. Requires a provisioned SendGrid subuser; accounts get one shortly after signup.

Authorizations:
oauth2
Request Body schema: application/json
domain
required
string <= 191 characters

Domain name (e.g., example.com). Do not include @ or protocol.

Responses

Request samples

Content type
application/json
{
  • "domain": "string"
}

Response samples

Content type
application/json
{
  • "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"
}

Get domain with DNS records

Returns domain details including DNS records needed for verification

Authorizations:
oauth2
path Parameters
id
required
integer

Responses

Response samples

Content type
application/json
{
  • "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"
}

Delete a sender domain

Removes the domain from SendGrid and soft-deletes locally

Authorizations:
oauth2
path Parameters
id
required
integer

Responses

Response samples

Content type
application/json
{
  • "message": "string"
}

Validate domain DNS records

Triggers DNS validation for the domain via SendGrid. Returns whether validation passed and any errors for specific DNS records.

Authorizations:
oauth2
path Parameters
id
required
integer

Responses

Response samples

Content type
application/json
{
  • "message": "string",
  • "valid": true,
  • "domain": {
    }
}

Users

Get current user information

Authorizations:
oauth2

Responses

Response samples

Content type
application/json
{
  • "id": 0,
  • "name": "string",
  • "email": "[email protected]",
  • "contacts_count": 0,
  • "contact_limit": 0,
  • "created_at": "2019-08-24T14:15:22Z",
  • "updated_at": "2019-08-24T14:15:22Z"
}

Contact Fields

List user contact fields

Returns custom contact fields defined by the user (20 per page)

Authorizations:
oauth2

Responses

Response samples

Content type
application/json
{
  • "data": [
    ],
  • "current_page": 0,
  • "total": 0,
  • "per_page": 0
}

Create a custom contact field

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.

Authorizations:
oauth2
Request Body schema: application/json
Any of
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.

Responses

Request samples

Content type
application/json
{
  • "label": "string",
  • "contact_field_type_id": "0abe5601-7738-43c8-858f-81911ecf89ee",
  • "type": "text"
}

Response samples

Content type
application/json
{
  • "id": 0,
  • "label": "string",
  • "name": "string",
  • "type": "text"
}

Get a specific contact field

Authorizations:
oauth2
path Parameters
id
required
integer

Responses

Response samples

Content type
application/json
{
  • "id": 0,
  • "label": "string",
  • "name": "string",
  • "type": "text"
}

Update a contact field label

Updates the label and auto-regenerates the name slug. Note: contact_field_type_id is immutable and cannot be changed after creation.

Authorizations:
oauth2
path Parameters
id
required
integer
Request Body schema: application/json
label
required
string <= 191 characters

Responses

Request samples

Content type
application/json
{
  • "label": "string"
}

Response samples

Content type
application/json
{
  • "id": 0,
  • "label": "string",
  • "name": "string",
  • "type": "text"
}

Delete a contact field

Permanently deletes the contact field

Authorizations:
oauth2
path Parameters
id
required
integer

Responses

Response samples

Content type
application/json
{
  • "message": "string"
}