API

REST API

Every object you can touch in the dashboard is reachable over HTTP, with scoped API keys so a key can read contacts without being able to send.

API keys

Screenshot pending. The API keys screen with a key open and a subset of the eight scopes ticked, so the scope model is visible rather than only described.

What it does

The dashboard is one client of the API, not a privileged one. Contacts, tags, sequences, enrollments, templates, sending and analytics are all addressable over plain HTTP with a bearer token, and the whole reference is published, including a markdown copy of every page.

Keys carry scopes, and a request is rejected unless the key holds every scope the endpoint requires. Every one of them, not any one of them. A key you hand to a reporting script can read analytics without being able to send a single message.

There are eight scopes: contacts:read, contacts:write, sequences:read, sequences:write, templates:read, templates:write, analytics:read and emails:send. They are deliberately coarse enough to reason about and fine enough to matter.

The shape of it

  1. 01

    Mint a scoped key

    Pick only the scopes the caller needs out of the eight.

  2. 02

    Send a bearer token

    One header. No session, no cookie, no CSRF token.

  3. 03

    Get a real status code

    Including a 403 when the key is missing a scope the route needs.

  4. 04

    Respect four windows

    Per minute, per five minutes, per hour and per day, all at once.

How it works

Authentication is one header. A key that lacks a scope the route requires gets a 403 rather than a silent empty result, which is the difference between a permissions problem you find in five minutes and one you find in production.

Rate limits are published rather than discovered: 60 requests a minute, 150 in five minutes, 300 an hour and 2,000 a day. All four windows apply simultaneously, and the tightest one you breach is the one that rejects the request with a 429. Knowing them up front is what lets you size a sync job correctly the first time.

Writes are built for scripts that run more than once. The contact upsert matches on email, so a nightly sync can run twice, or be re-run after a failure, without creating a second copy of anybody.

Scopes are checked per request
# This key holds analytics:read and nothing else.
curl https://api.sendheron.com/api/v1/contacts \
  -H "Authorization: Bearer $SENDHERON_REPORTING_KEY"

# 403. GET /contacts requires contacts:read, and a key
# that cannot read contacts certainly cannot send to them.
  • Scoped bearer tokens across eight permissions
  • Documented rate limits, applied per window
  • Idempotent upsert on email, so a sync job can run twice safely
  • Standard status codes, including 403 when a key lacks a scope

Read the API reference

What makes it different, honestly

Nothing here is a second surface bolted on after the dashboard shipped. The same endpoints the interface calls are the endpoints you call, which is why the reference can be exhaustive rather than a curated subset.

The documentation is machine-readable on purpose. Every endpoint page has a markdown twin at the same address with .md appended, the entire reference is available as one markdown document, and the site publishes an llms.txt index. A tool that reads the docs does not have to parse our HTML, and neither does a person: there is a button on the reference that copies the whole thing in a form you can paste into a model.

What the reference does not include is a way for SendHeron to call you back. There are no outbound webhooks documented today: you can drive it, but it will not notify your application when something happens. During the private beta, keys are also issued with the account rather than minted from a self-serve settings page.

Questions

REST API, in detail

Private beta

REST API, live in the private beta

We onboard a small number of teams at a time and review every account. That is how we keep deliverability high for everyone.