Endpoints
The supported SendSets agent-native REST contract.
All customer API paths use /v1 and bearer authentication. API keys begin
with ssk_. Run creation is asynchronous and returns 202 Accepted with the
durable state.
Runs
| Method | Path | Scope |
|---|---|---|
POST | /runs | WRITE_RUNS; launch intent also needs EXECUTE_RUNS |
GET | /runs | READ_RUNS |
GET | /runs/:id | READ_RUNS |
POST | /runs/:id/cancel | WRITE_RUNS |
POST /runs requires Idempotency-Key. Its body accepts an existing campaign
or campaign metadata, auto/explicit/tag sender selection, inline leads,
contact IDs, segment IDs, ordered steps and variants, a daily limit, schedule,
stop_on_reply, opt-out state, and draft or launch intent.
An accepted launch moves through queued, preflighting, and then one of
awaiting_approval, launching, active, failed, or denied. Later states
are paused, completed, and cancelled. GET /runs/:id includes its
append-only event stream.
Campaign variant dimensions
| Method | Path | Scope |
|---|---|---|
GET | /campaigns/:id/ab-variants | READ_CAMPAIGNS |
POST | /campaigns/:id/ab-variants | WRITE_CAMPAIGNS |
PATCH, DELETE | /campaigns/:id/ab-variants/:variantId | WRITE_CAMPAIGNS |
Variant create and update bodies accept optional text fields offer, angle,
and cta, alongside the existing copy, weights, and metadata. An omitted field
keeps its default on create or its current value on update; an empty string
clears a label. Inline variants in campaign creation accept these fields too.
Variant responses include the three labels plus parent_variant_id,
generation, changed_dimension, generator_model, and prompt_version.
Lineage fields are reserved for generated descendants and cannot be set through
these create or update requests.
A contact's step assignment freezes the selected arm, template revision, and labels before dispatch. Weight or copy edits affect only steps that have not been assigned. Retries keep the stored selection. Campaign copies preserve variant labels but start with fresh assignments and send history. See A/B variants.
Inbox-agent approval retries
POST /unibox/agent-drafts/:id/approve requires Write Unibox for API keys and Access Unibox for workspace members. The draft ID is the operation's natural idempotency key. Approval and send-task creation commit together. A retry with the same body, or an omitted body, returns the original task_id, scheduled_at, and send_mode. An omitted body keeps the previously approved edit. A changed body or a discarded draft returns 409 with machine-readable code conflict and a request_id. Approval queues a reply; it does not confirm delivery.
A queue-registration error can occur after this transaction commits. Retrying recovers the same task. The backend also repairs missing queue registrations on startup and every minute. Retries after task pruning do not recreate the send. Historical approvals without recorded task ownership and imported approvals cannot be requeued through this endpoint.
Draft list, count, and detail endpoints expose only completed, charged drafts. In-flight generation and billing recovery are internal states. They cannot be approved or discarded through the draft endpoints.
Reply routing and text model
GET /organization/current and PATCH /organization/current expose two additive
workspace settings under the existing Manage settings permission:
reply_agent_categories: an array containing any ofpositive,question,objection, orreferral, without duplicates. The default is["positive"]. An empty array disables reply generation. Other values return400 bad_request.reply_agent_model: an OpenRouter text-model ID of at most 200 bytes, or an empty string to use the instance default. Jev decision models are rejected. This model writes and validates the draft while the reply agent settings below usemodel_source: platform; Jev only classifies.
Omitting either field preserves its value. Settings changes are audited and refresh teammates' workspace settings through the existing realtime audit event. Only selected categories reach the LLM. Failed or unclear classifications do not produce a draft or drafting charge. Automatic sending rechecks the category selection at dispatch. These settings do not enable drafting or automatic sending.
Inbox-agent draft categories
Inbox-agent draft responses include reply_category,
reply_category_confidence (0 to 1), and reply_category_source (empty,
header, lexicon, or model). Categories are positive, question,
objection, referral, rejection, automated_reply, unsubscribe, spam,
and unclear. These fields are independent of the existing campaign
intent_class and confidence; older drafts default to unclear with zero
confidence and an empty source. A model source identifies Jev classification
when OpenRouter Jev or direct TypeSafe is configured. Unavailable or invalid
model classification does not create a draft. Only eligible human replies in
the workspace's selected categories reach the reply model.
See Inbox agent.
Inbox-agent factual support
Draft responses also include a grounding object:
status:unverifiedfor historical drafts,review_requiredfor missing or insufficient support, ormodel_supportedfor a model assessment meeting the workspace response and classification confidence thresholds at generation time.reason: empty,no_product_sources,validation_unavailable,invalid_validation,unsupported_claim,low_response_confidence,knowledge_unavailable, orlow_classification_confidence.confidence: the factual-check model's confidence from 0 to 1; zero when no valid assessment is available. This is separate from category confidence.sources: internal references withidandsha256, pluscontentfor exact evidence in newly generated drafts and optionallabel. Workspace IDs areworkspace:product_description,workspace:reply_agent_offer, andworkspace:reply_agent_signup_url. Retrieved excerpts useknowledge:<version UUID>:<position>. Historical references may omit content.claim_sources: the IDs cited for supported claims. This is empty when validation does not establish support.
The check applies to the generated draft body, including its final signup link. Human edits do not update the assessment. It is advisory and may be incorrect; draft mode requires human approval, while automatic mode requires the explicit grant and send-time checks described below. Validation failures retain a reviewable draft rather than authorizing a send. Historical rows default to unverified, zero confidence, an empty reason, and empty source arrays.
Reply Agent workspace settings
The existing JWT workspace endpoints GET /organization/current and
PATCH /organization/current expose reply_agent_signup_url and
reply_agent_offer alongside inbox_agent_enabled. PATCH requires the
Manage settings organization permission. Both new fields are optional
strings: omission keeps the current value, and an empty string clears it.
Setting these fields repeatedly to the same values is naturally safe to retry.
The signup URL must be an absolute HTTPS URL without embedded credentials or
whitespace, at most 2,048 bytes. The offer supports up to 2,000 characters.
Invalid settings return 400 with the existing bad_request code and
request_id. Newly generated positive drafts include the exact configured
signup URL. These settings do not authorize automatic sending.
The same endpoints expose numeric reply_agent_classification_confidence
(default 0.95) and reply_agent_response_confidence (default 0.90). PATCH
accepts finite fractions greater than 0 and at most 1; omission preserves
values. Invalid values return 400 with bad_request. Thresholds apply to new
drafts, including recovered generation; existing drafts are not reassessed.
Low response confidence flags factual support for review. If factual support
passes but category confidence is too low, the draft receives
low_classification_confidence. Human edits do not update confidence or factual support. Negative product claims require explicit evidence, so absent documentation cannot establish that a feature is unsupported.
The workspace endpoints also expose reply_agent_mode (draft, the default, or
automatic), reply_agent_thread_cap (default 3, integer 1–100), and
reply_agent_daily_cap (default 100, integer 1–10000). Omission preserves
values. Invalid settings return 400 with bad_request and request_id.
Changing mode alone grants no sending authority. Automatic candidates are new
drafts generated in automatic mode under an existing grant. Queued replies
recheck the current grant, settings, recipient safety, mailbox/worker health,
subscription, budgets, and manual replies immediately before dispatch.
Human-created automatic reply grant
These routes are JWT-only. They do not accept API keys or OAuth credentials.
| Method | Path | Organization permission | Behavior |
|---|---|---|---|
| GET | /organization/current/reply-agent/grant | Manage settings | Returns { "data": grant }, or data: null |
| PUT | /organization/current/reply-agent/grant | Manage settings and Access Unibox | Creates or replaces the workspace's grant |
| DELETE | /organization/current/reply-agent/grant | Manage settings | Revokes the grant; returns data: null |
PUT requires mailbox_ids (1–100 unique workspace mailbox UUIDs), max_per_day
(integer 1–10000), and expires_at (a future timestamp within one year).
The response includes id, organization_id, those three fields,
created_by, and created_at. Repeating the same complete PUT body preserves
the grant ID; changing any authority field creates a new ID and holds replies
already queued under the previous grant. A delayed PUT retry after a newer
replacement restores the older authority, following replacement semantics.
PUT and DELETE are naturally safe to retry; no Idempotency-Key is needed.
Invalid input or foreign mailbox IDs return 400 with bad_request and
request_id. A missing permission returns 403. Grant changes publish the
organization audit event.
Held drafts expose automatic_reason, a machine-readable reason such as
grant_required, grant_changed, policy_held, automatic_disabled,
confidence_requires_review, assessment_requires_review, thread_cap,
workspace_daily_cap, mailbox_daily_cap, inbound_requires_review,
recipient_mailbox_or_thread_changed, workspace_suspended,
entitlement_required, credits_exhausted, usage_budget_reached, or
policy_unavailable. Held replies use the normal human approval endpoint;
automatic retries never create another task for the same action.
Reply agent activity summary
GET /unibox/agent-drafts/summary?days=7 requires Read Unibox for API keys and
Access Unibox for workspace members. days is an integer from 1 to 90
(default 7); anything else returns 400 with bad_request and request_id.
It returns { "data": summary } with, for drafts created in the window,
drafted, sent (approved, by a person or automatically),
sent_automatically, discarded, interested (category positive),
median_minutes_to_send, categories (category and count) and daily
(date, drafted, sent, one row per UTC day). pending and held are the
drafts waiting for review right now, whatever the window. recent lists the
12 most recently changed drafts with id, thread_id, to_addr, subject,
reply_category, status, automatic, automatic_reason, created_at and
updated_at; it carries no message or draft body. last_activity_at is the
latest draft change, or null. It is read-only.
Reply classifier labels
These routes are JWT-only. They do not accept API keys or OAuth credentials.
| Method | Path | Organization permission | Behavior |
|---|---|---|---|
| GET | /organization/current/reply-agent/labels | Access Unibox | Returns { "data": [rule] }, an empty array when none are set |
| PUT | /organization/current/reply-agent/labels | Manage settings | Replaces every rule with { "rules": [rule] } |
A rule is { "label", "description", "phrases" }. label is 1 to 40
lowercase letters, digits or hyphens, unique in the workspace; description is
optional, at most 200 characters; phrases holds 1 to 20 phrases of 2 to 80
characters, stored lowercased with whitespace collapsed and duplicates
dropped. At most 12 rules. Invalid input returns 400 with bad_request and
request_id. PUT is a full replace, so retries are naturally safe and no
Idempotency-Key is needed. It publishes the organization audit event. An
inbound message containing a phrase as whole words gets the label on its
conversation; see your own labels.
Reply agent model, classifier and test chat
These session routes are JWT-only. API keys and OAuth tokens reach the same settings through the reply agent API.
| Method | Path | Organization permission | Behavior |
|---|---|---|---|
| GET | /organization/current/reply-agent/settings | Manage settings | Returns { "data": settings }, with defaults when never saved |
| PUT | /organization/current/reply-agent/settings | Manage settings | Replaces the model source, endpoint, model, classifier and instructions |
| POST | /organization/current/reply-agent/openrouter | Manage settings | Exchanges an OpenRouter PKCE code and code_verifier for a key and stores it |
| DELETE | /organization/current/reply-agent/key | Manage settings | Forgets the workspace key and returns to the instance model |
| POST | /organization/current/reply-agent/test | Manage settings | Answers a test conversation with the saved settings; stores and sends nothing |
settings holds model_source (platform, openrouter or custom),
base_url, model, api_key_set, api_key_hint (last four characters),
classifier (calibrated, rules or llm), instructions (at most 4,000
characters), updated_at, and three instance facts: platform_model_available,
platform_model and calibrated_classifier_available. The key itself is never
returned.
PUT takes the same fields plus an optional api_key. Omitting api_key keeps
the stored key only while model_source and base_url are unchanged; any other
change forgets it, so a key is never sent to a host it was not saved for.
platform always forgets the key. openrouter needs a key (stored or in the
body) and defaults model to openai/gpt-4o-mini. custom needs base_url
(HTTPS, publicly routable, no credentials, query or fragment) and model.
Repeating the same body leaves the same state, and DELETE is a no-op when no key
is stored, so neither needs an Idempotency-Key. Each write publishes the
organization audit event without the key.
The test body is { "subject": "...", "messages": [{ "role": "prospect" | "agent", "content": "..." }] }
with 1 to 20 turns of at most 8,000 characters, ending with a prospect turn. The
response holds category, category_confidence, category_source, drafted,
skip_reason (declined_by_rules, declined_by_classifier or category_not_enabled), reply,
grounding, automatic_eligible, model_source, model, tokens_used and
credits_charged (5 on the instance model when a reply is drafted, otherwise
0). Errors: 400 for invalid input or no configured model, 402 with
insufficient_credits, 403 without the paid entitlement, 429 at the usage
cap, 503 when the model fails (the message includes the provider's error).
Every error carries code and request_id. The test endpoint takes no
Idempotency-Key because it changes nothing but the credit balance: each call
that drafts a reply on the instance model is charged, so retry it only when you
want another answer. The OpenRouter exchange is naturally safe to retry,
because OpenRouter accepts each code once and a replayed code changes nothing.
Product knowledge endpoints
JWT workspace sessions with Manage settings can manage up to 20 active text or Markdown sources:
| Method | Path | Behavior |
|---|---|---|
GET | /organization/current/reply-knowledge/sources | Returns all active sources as { data: [version, ...] }, ordered by creation time and ID; this bounded configuration list is unpaginated |
PUT | /organization/current/reply-knowledge/sources/:sourceID | Creates or replaces the source with { title, content }; the caller supplies a stable UUID |
DELETE | /organization/current/reply-knowledge/sources/:sourceID | Deactivates only that source, retaining history and evidence |
GET | /organization/current/reply-knowledge | Returns { data: version } or { data: null } |
POST | /organization/current/reply-knowledge/upload-preview | Extracts a multipart file into { data: { title, content } } without saving |
PUT | /organization/current/reply-knowledge | Replaces the active document with { title, content } |
DELETE | /organization/current/reply-knowledge | Clears the active selection, preserving version history and existing draft evidence |
A version contains id, source_id, organization_id, title, content, sha256, and created_at. Title is required, trimmed, and limited to 200 characters. Content is required, trimmed, valid UTF-8 without NUL, and limited to 65,536 bytes. Invalid bodies return 400 with code: bad_request and request_id. Save failures leave that source’s previous document active. Invalid source UUIDs and saves exceeding 20 active sources return 400 with code: bad_request. Existing-source replacements remain allowed at the cap. PUT is naturally idempotent: identical normalized title and content within the same source reuse the immutable version; DELETE retries are harmless. Concurrent successful replacements publish complete versions, with the last committed replacement active. Retrying an older PUT after a later save selects that older content again, as with other replacement operations.
The original GET/PUT/DELETE endpoints continue to read, replace, and clear only the legacy source (00000000-0000-0000-0000-000000000000), preserving their response shapes. Clearing it does not clear other sources. Source-specific PUT and DELETE are naturally retry-safe and need no Idempotency-Key. Reuse the same source UUID across retries; using a new UUID creates an independent source.
Upload preview accepts one .txt, .md, .markdown, or .pdf file. Text must be valid UTF-8, optionally with a BOM, and fit within 64 KiB. PDFs must fit within 5 MiB and yield at most 64 KiB of text within ten seconds. Encrypted PDFs, scanned PDFs without text, unsupported extensions, malformed files, and excessive output return 400 with code: bad_request and request_id. A missing PDF extraction runtime returns 503 with code: service_unavailable. The title defaults to the filename without its extension and must meet the same title limit. Preview is read-only and naturally safe to retry; it needs no Idempotency-Key. Review its text, then PUT it to publish a version. Original files are not retained.
These session endpoints are JWT-only; API keys and OAuth tokens use the reply agent API. They do not enable sending or change existing workspace response shapes. Changes publish the organization audit event so teammates' workspace queries refresh live. See Inbox agent for retrieval limits.
Reply agent over the API
API keys and OAuth tokens with AI_AGENT, and dashboard members with Manage settings, configure the reply agent under /reply-agent. Each route runs the same handler and validation as its session counterpart. The automatic-sending grant and the OpenRouter key exchange stay session-only: a credential can switch the agent on and set its mode, but no automatic reply is sent until a person creates a grant in the dashboard.
| Method | Path | API permission | Organization permission | Behavior |
|---|---|---|---|---|
| GET | /reply-agent/config | AI_AGENT | Manage settings | Returns { "data": config } |
| PATCH | /reply-agent/config | AI_AGENT | Manage settings | Changes only the fields sent; returns the updated config |
| GET | /reply-agent/settings | AI_AGENT | Manage settings | Same as GET /organization/current/reply-agent/settings |
| PUT | /reply-agent/settings | AI_AGENT | Manage settings | Same as PUT /organization/current/reply-agent/settings |
| POST | /reply-agent/test | AI_AGENT | Manage settings | Test chat; stores and sends nothing, and charges AI credits on the SendSets model |
| GET | /reply-agent/labels | READ_UNIBOX | Access Unibox | Your classifier labels |
| PUT | /reply-agent/labels | AI_AGENT | Manage settings | Replaces every label rule |
| GET, PUT, DELETE | /reply-agent/knowledge | AI_AGENT | Manage settings | The legacy knowledge source, as above |
| GET | /reply-agent/knowledge/sources | AI_AGENT | Manage settings | Every active knowledge source |
| PUT, DELETE | /reply-agent/knowledge/sources/:sourceID | AI_AGENT | Manage settings | Create, replace or deactivate one source |
| POST | /reply-agent/knowledge/upload-preview | AI_AGENT | Manage settings | Extracts a file into { title, content } without saving |
config holds inbox_agent_enabled, reply_agent_signup_url, reply_agent_offer, reply_agent_classification_confidence, reply_agent_response_confidence, reply_agent_mode, reply_agent_thread_cap, reply_agent_daily_cap, reply_agent_categories, reply_agent_model and product_description. PATCH accepts any of them with the limits described in Reply Agent workspace settings; omitted fields are unchanged, an empty string clears a text field, and invalid values return 400 with code: bad_request and request_id. PATCH and the PUT routes set state to the values sent, so a retry is naturally safe and needs no Idempotency-Key. Every change publishes the organization audit event.
Agent policies and approvals
| Method | Path | Caller |
|---|---|---|
GET | /agent-policies/effective | API key, OAuth, or JWT |
GET, POST | /agent-policies | JWT owner/settings manager only |
PUT, DELETE | /agent-policies/:id | JWT owner/settings manager only |
GET | /approvals | JWT send manager only |
POST | /approvals/:id/decision | JWT send manager only |
Policies bind to one API key or OAuth grant. Capabilities are campaign.launch,
email.compose, inbox.reply and mailbox.provision; the last is autonomous
only with a max_monthly_mailbox_spend_cents the order's monthly total fits
under. Autonomous grants require explicit
mailbox IDs, independent capabilities, recipient/run and daily limits, action
limits, and an expiry. Approval records expire after 30 minutes. Approving a run
causes preflight and all hard safety gates to run again immediately before the
campaign starts.
Direct sends
| Method | Path | Scope | Capability |
|---|---|---|---|
POST | /emails/:id/send | SEND_DIRECT_EMAIL | email.compose |
POST | /unibox/compose | SEND_DIRECT_EMAIL | email.compose |
POST | /unibox/reply | SEND_INBOX_REPLY | inbox.reply |
API-key and OAuth callers receive an executed or awaiting_approval result.
JWT browser sessions are already human-authorized. Suppressions, organization
limits, mailbox holds/authentication/health, and deliverability rules remain
authoritative regardless of policy.
App connections and product events
| Method | Path | Scope |
|---|---|---|
GET, POST | /app-connections | INTEGRATIONS |
GET, PATCH, DELETE | /app-connections/:id | INTEGRATIONS |
POST | /app-connections/:id/rotate-secret | INTEGRATIONS |
POST | /app-connections/:id/test | INTEGRATIONS |
POST | /events | WRITE_EVENTS; requires Idempotency-Key |
POST | /events/test | WRITE_EVENTS; stores nothing |
GET | /events | READ_CAMPAIGNS |
GET | /events/schema | READ_ANALYTICS |
GET | /events/:id | READ_CAMPAIGNS |
POST | /identities/link | WRITE_EVENTS |
An app connection is the base URL and signing secret an app_action step
calls. POST /app-connections/:id/test sends a signed app.ping and answers
200 with a checklist (dns, tls, connection, authentication, status,
latency_ms, error) whether or not the app accepted it. POST /events
records a product event for a contact or, with an identity object, for a
person who may not be a contact yet, resumes every wait_for_event step open
for it, and evaluates goals; the 202 names each resumed step under
matched_runs.
Goals, identities, and acquisition reports
| Method | Path | Scope |
|---|---|---|
GET | /goals, /goals/:id | READ_ANALYTICS |
POST | /goals | MANAGE_ACQUISITION |
PATCH, DELETE | /goals/:id | MANAGE_ACQUISITION |
POST | /goals/preview, /goals/:id/replays/preview | READ_ANALYTICS; write nothing |
POST | /goals/:id/replays | MANAGE_ACQUISITION |
GET | /goals/:id/replays, /goals/:id/replays/:replayId | READ_ANALYTICS |
GET | /people, /people/:id | READ_CONTACTS |
DELETE | /people/:id | MANAGE_ACQUISITION |
GET | /identity-conflicts | READ_CONTACTS |
POST | /identity-conflicts/:id/resolve | MANAGE_ACQUISITION |
GET | /acquisition/summary, /acquisition/breakdown | READ_ANALYTICS |
POST | /acquisition/event-trends, /acquisition/funnels | READ_ANALYTICS; write nothing |
GET | /acquisition/prospects, /acquisition/prospects/:contactId/timeline | READ_ANALYTICS and READ_CONTACTS |
PUT, DELETE | /acquisition/campaign-spend/:campaignId | MANAGE_ACQUISITION |
GET | /acquisition/attribution/settings, /acquisition/attribution/summary, /acquisition/attribution/breakdown | READ_ANALYTICS |
PUT | /acquisition/attribution/settings | MANAGE_ACQUISITION |
GET | /acquisition/attributions, /acquisition/attributions/:personId, /acquisition/prospects/:contactId/attribution | READ_ANALYTICS and READ_CONTACTS |
PUT, DELETE | /acquisition/attributions/:personId/correction | MANAGE_ACQUISITION |
JWT callers need view_analytics for reads, view_contacts as well for
prospects and people, and manage_settings for anything that changes goals,
identities, spend, or attribution. Attribution settings and correction writes are replacement operations, naturally safe to retry. PUT spend and POST /identities/link land on the same
state when repeated, so they need no Idempotency-Key; the other writes accept
one. Details are in Goals and acquisition.
Lead database
| Method | Path | Scope |
|---|---|---|
POST | /lead-database/people, /lead-database/companies, /lead-database/coverage, /lead-database/facets, /lead-database/export | READ_CONTACTS |
JWT callers need view_contacts. These are read-only POSTs, so conditions
travel as JSON and retries are naturally safe. Details are in
Lead database.
Outbound insights
| Method | Path | Scope |
|---|---|---|
GET | /analytics/insights/overview, /analytics/insights/breakdown, /analytics/insights/infrastructure | READ_ANALYTICS |
JWT callers need view_analytics. All three are read-only. Details are in
Analytics and audit.
Mailboxes
| Method | Path | Scope |
|---|---|---|
GET | /emails | READ_EMAILS |
POST | /emails | WRITE_EMAILS (SMTP/IMAP only) |
GET | /emails/:id | READ_EMAILS |
PATCH, DELETE | /emails/:id | WRITE_EMAILS |
POST | /emails/:id/test | WRITE_EMAILS |
GET | /emails/:id/warmup | READ_EMAILS |
POST | /emails/:id/warmup/start, /pause, /resume, /stop | WRITE_EMAILS |
POST | /emails/onboarding/oauth/cli-start | WRITE_EMAILS |
GET | /emails/onboarding/oauth/session/:session | WRITE_EMAILS |
POST /emails connects an SMTP/IMAP mailbox after a worker has validated the
credential; Google and Microsoft mailboxes go through cli-start, which
returns a consent URL and a session to poll. POST /emails/:id/test dials the
stored credential again and answers 200 with a checklist even when it fails.
GET /emails/:id carries the campaigns the mailbox sends for and its recent
errors beside the mailbox itself.
Managed mailboxes
| Method | Path | Scope |
|---|---|---|
GET | /mailboxes/provision/providers | READ_EMAILS |
GET | /domains | READ_EMAILS |
POST | /domains, /domains/:name/verify, /domains/:name/connect, /domains/:name/connection/verify | MANAGE_MAILBOX_PROVISIONING |
POST | /domains/quote | MANAGE_MAILBOX_PROVISIONING |
POST | /domains/order | MANAGE_MAILBOX_PROVISIONING; requires Idempotency-Key |
GET | /mailboxes/domains/renewals | READ_EMAILS |
POST | /mailboxes/provision/quote | MANAGE_MAILBOX_PROVISIONING |
POST | /mailboxes/provision | MANAGE_MAILBOX_PROVISIONING; requires Idempotency-Key |
GET | /mailbox-provisioning, /mailbox-provisioning/:id | READ_EMAILS |
POST | /mailbox-provisioning/:id/cancel | MANAGE_MAILBOX_PROVISIONING |
renewals lists every managed domain's renewal cycle: when it renews, what
was charged, and whether anything needs a person. Renewal is automatic and
priced at the provider's current renewal cost plus processing, read fresh each
cycle.
providers lists the live Google and Microsoft mailbox prices and the Azure
tenant price. A zero or missing Google or Microsoft cost uses $3 per mailbox
per month. Failed pricing calls return 503 pricing_unavailable.
A quote prices an order from the upstream provider's live costs and checks
every domain; it is valid for fifteen minutes, after which those prices are no
longer guaranteed. Pass domain for one domain, or domains for several: the
mailboxes are spread across them as evenly as possible, and each domain is
priced on its own because registration cost varies by TLD. Provisioning turns a quote into an
order once the caller may spend: a session directly, a credential through an
agent policy that grants mailbox.provision with a
max_monthly_mailbox_spend_cents the order fits under, or a human approval
(the 202 awaiting_approval shape, re-submitted with the same quote_id).
The order then waits on a Stripe checkout link (awaiting_payment) and moves
through paid, queued, provisioning, configuring and ready, or
provisioning_failed when a paid provider purchase needs support. The order
shows planned mailbox addresses before adoption. Domain-only registration uses
payment Checkout; recurring mailbox and Azure orders use subscription Checkout.
Managed purchases are offered on SendSets Cloud only; a self-hosted instance answers
503 mailbox_provisioning_unavailable.
Campaign checks
| Method | Path | Scope |
|---|---|---|
POST | /campaigns/:id/validate | READ_CAMPAIGNS |
POST | /campaigns/:id/preflight | SEND_CAMPAIGNS |
POST | /campaigns/:id/test | SEND_CAMPAIGNS (sends real mail) |
GET | /campaigns/:id/test-runs, /campaigns/:id/test-runs/:runId | READ_CAMPAIGNS |
POST | /campaigns/:id/test-runs/:runId/cancel | SEND_CAMPAIGNS |
GET | /system/readiness | any credential |
validate is the read-only twin of preflight: the same checks plus the
structural checks on the step graph, answered as problems with a fix each,
without storing a report. /system/readiness is what sendsets doctor reads:
the workflow sidecar, live workers, the scheduler, and whether inbound events
are accepted.
Other supported surfaces
The default contract also mounts authentication, campaigns and sequences, contacts/leads and segments, mailboxes and warmup, inbox, analytics and deliverability, suppressions, realtime/audit events, API keys/OAuth, and webhooks. The generated OpenAPI document is the field-level reference.
CRM, forms, automations, advisor, templates, integrations, and internal AI
engines remain compiled for later reuse but return 404 by default. Operators
can mount them explicitly with SENDSETS_ENABLE_EXTENDED_SURFACES=true; they
are outside this supported contract.
Agent protocols
| Method | Path | Caller |
|---|---|---|
POST | /mcp | API key or OAuth; each tool gated by its own scope. See MCP server |
POST | /a2a | API key or OAuth; each tool gated by its own scope. See A2A agent |
GET | /.well-known/agent-card.json | Public, unversioned. The A2A Agent Card |
Customer Stripe recording preview
POST /api/v1/integrations/inbound/stripe/:orgID/:mode is a public provider webhook, authenticated by Stripe-Signature over the raw request body, with mode equal to test or live. It requires an existing Stripe connection configured through the extended integrations surface. The endpoint accepts snapshot events from the account itself, rejects Connect events and mismatched modes, and caps bodies at 1 MiB. It does not accept SendSets API keys as webhook authentication.
A 200 response contains recorded: true and duplicate: true|false only after a durable encrypted receipt is committed. Stripe's event ID provides delivery idempotency; Idempotency-Key is not used. Conflicting reuse returns 409; invalid signatures, modes, or envelopes return 400; unavailable connections return 404; encryption or persistence failures return 500 and should be retried. This preview does not yet normalize payments or change acquisition reports. Setup reuses the existing integration routes, with Manage settings for JWT callers, the Integrations permission for API keys, and the paid-plan gate for connect. See Stripe setup.
B2B database
The shared prospect catalog is separate from the workspace lead database.
| Method | Path | Permission |
|---|---|---|
GET | /b2b-database/metadata | READ_CONTACTS |
POST | /b2b-database/people, /b2b-database/companies | READ_CONTACTS |
POST | /b2b-database/import | READ_CONTACTS and WRITE_CONTACTS |
