API Overview
The SendOps Public API lets you read events, manage configuration, and inspect account state programmatically. It is the same data plane your dashboard uses, exposed under https://api.sendops.dev and authenticated with an API key you create in the dashboard under Workspace → API Keys.
This API does not send single messages
The Public API has no single-message send endpoint. For per-message transactional email, call SES directly with your own AWS credentials, or use the Send API, a separate API on ingest.sendops.dev that is in early access. The Public API can trigger bulk sends: POST /v1/broadcasts and the broadcast test-send endpoint compose a message and dispatch it. Whichever way a message is sent, it goes out through SES in your own AWS account, on your identities and your reputation, never through mail infrastructure of ours.
What the API gives you
- Message-level observability — list, search, and trace individual messages and the SES events they emit.
- Reporting — deliverability and engagement aggregates, plus per-template performance.
- Suppressions — read the account-level suppression list and look up individual recipients.
- Configuration introspection — channels (SES configuration sets), templates, tracking domains, and SES identities. A channel’s SES configuration set is not named after the channel. It is
sesmail-{namespace}--{channel-slug}(note the double dash): for example,sesmail-acme--transactional. The namespace is fixed when your organization is created and does not change if you rename it later. If you call SES directly, read the exact name from the channel’sses_config_set_name(GET /v1/channels) rather than building it by hand. - Contacts & subscriptions — read your SES contact list, subscription topics, and contacts (attributes and per-topic preferences), and write them too: create, replace, patch, delete, upsert by external id, and bulk-import up to 5000 contacts per call.
- Lists & Segments — read and write static Lists and dynamic, SendQL-defined Segments, page their members, lint and size a SendQL predicate before you save it, pause or force-evaluate a Segment, and restore any definition from its version history.
- Attributes — read and write the custom-attribute registry (the schema Segment predicates compile against), with a dry-run that reports an edit’s impact on dependent Segments.
- Activities — read contact activity, and ingest free-form behavioral signals with
POST /v1/activities(single or batch). - Broadcasts — read broadcast definitions and delivery state, and create, send/schedule, test, and delete one-off bulk sends to a List or Segment.
- Workflows — author, validate, activate, pause and archive drip workflows written in SendFlow, and read their runs, exit/funnel analytics and per-contact timelines, plus a dry-run simulation against a sample contact and a shadow mode that rehearses on real data without mailing anyone.
- Templates — read, render and test-send templates, and
PUTone to create or replace it: the write stores the content and, for an email template, deploys it to your own SES account before it answers. A layout is stored but never deployed. - Tags — read the org’s tag vocabulary (Gmail-style folders across Lists, Segments, and Templates).
- Assets — upload, read and delete the images your templates reference: content-addressed bytes on your Edge CDN, with their logical paths, CDN URLs, dimensions, and the templates that reference each one.
- Bundles — export every definition the org holds as a deterministic
sendops.jsonbundle, and apply one back withPOST /v1/import— dry run by default. See Push from CI. - Domain Auth (DMARC) — read what is sending as your domains from DMARC aggregate reports, the policy ladder with every criterion beside its measured value, what publishing a stricter policy would cost, and the history of what changed on a domain — plus write source decisions, cost acknowledgements and timeline notes. No endpoint publishes DNS.
- Deliverability Score — read each verified sending domain’s 0–100 score, its band, the named checks behind it and a ranked list of tasks that each say where the fix is done (SendOps, DNS with the records to publish, AWS, or a third party). See Deliverability Score.
- API key requests — a credential asks for a narrower, expiring API key to hand to a sub-agent or another tool, an org admin approves it in the dashboard, and the same credential claims the secret once. See Requesting API keys for sub-agents.
- Account state — the calling org’s onboarding state, AWS connection status, plan, send quota, and SES production-access state.
Every endpoint is documented in the sidebar, grouped by resource. The catalogue is generated from the live OpenAPI spec.
What the API does not do (in v1)
- No per-message send endpoint. There is no “send this one email” call on
api.sendops.dev: transactional mail is sent by your application against SES directly, or through the Send API (early access), which has its own host,ingest.sendops.dev, and sends through your own SES account. Bulk sends are the exception here: Broadcasts can be created, sent, scheduled, and test-sent through this API, and every one of them fires through your own SES account rather than through SendOps infrastructure. - No browser-origin API calls. CORS is closed on
api.sendops.dev; API requests are server-to-server. The authorization server is the one exception —auth.sendops.devaccepts cross-origin requests so a browser-based public OAuth client can exchange its own tokens. - No infrastructure writes. Everything an org authors — contacts, topics, Lists, Segments, attributes, activity properties, templates, workflows, broadcasts and images — is writable, but AWS-facing configuration (channels, SES identities, tracking domains, suppressions) stays read-only here and is changed in the dashboard.
- No second owner of a definition. Every definition — template, Segment, List, topic, attribute, activity property, workflow — is writable by any caller holding the matching
.managescope. Nothing is read-only because it came from somewhere else, and a409is always about state (a key already taken, a staleexpected_content_hash, a dependent that still points at the row), never about provenance. Keeping your definitions in your own version control is supported and encouraged — you push them withPOST /v1/import, which is an ordinary authored write. - No Segment membership writes. A Segment’s members are computed from its predicate, never assigned. The
/v1/segments/previewand/v1/segments/validatePOSTs are non-mutating dry-runs that size and lint a predicate without saving anything.
Authentication at a glance
Every request carries an Authorization: Bearer sk_<env>_<...> header. Keys are scoped to a single organization and granted a subset of permissions (scopes) chosen when the key was created. Live and test keys share the same scope vocabulary; requests are unthrottled until a per-org rate limit is configured, and when one is, test keys run at 10% of the live ceiling.
See Authentication for the full key lifecycle.
Conventions
- All endpoints are versioned under
/v1/and use JSON request/response bodies. There is no XML or form-encoded alternative. - Timestamps are ISO 8601 in UTC (
2026-05-17T19:00:00Z). - IDs are UUIDs in canonical hyphenated form — but you rarely need one. Wherever the API names an object, it also accepts that object’s own public handle: a List, Segment or workflow
key, an attributename, a template file name, a topic name, a channel name, an asset’s source path, or a contact’s email orexternal_id. Prefer the handle — it is immutable, it reads in a config file, and a reviewer can tell what it points at. One exception to watch: naming a contact by email orexternal_idon a route outside the contacts surface — a workflow dry-run, an enrolment-decision filter, a List or Segment membership check — additionally needsapi.contacts.view, because turning an address into a contact is a contacts read. The contact’s id form needs only that route’s own scope. - Errors follow RFC 7807
application/problem+json. - Lists are cursor-paginated; offsets and totals are not exposed.
- Rate-limit headers (both
RateLimit-*and legacyX-RateLimit-*) appear on every response once a per-org limit is configured; until then requests are unthrottled. See Rate Limiting.
Getting your first response
You’ll need: a SendOps account, an API key with at least one scope (e.g., api.reports.view), and a terminal. Head to the Quickstart for a five-minute walk-through.