API, integration and operations

A contract before the code, a production nobody touches by hand

Everything the web console does can also be done through the API, under the same authorisations. The contract is written before the implementation, published, and compared on every integration run; how the software reaches production is part of compliance.

Published contractbefore the code, comparedon every integration runCallspace derived from the keyidempotent · 404 outsideEventdated logopaque identifiersSigned webhookspaced retriessecret per subscriptionread by cursorNo personal data on the way outopaque identifiers only
The contract comes before the code; what goes out carries opaque identifiers only.

Contract and conventions

Nothing is implemented before it is described. The contract is the source of truth for consumers.

  • Everything is a resource with a prefixed opaque identifier; no sequential identifier is ever exposed.
  • Cursor pagination, never offsets: a page stays stable even as the collection moves.
  • Idempotent creations: an idempotency key replayed within twenty-four hours returns the original response, with no side effects.
  • RFC 9457 errors, with a stable business code and a request identifier that can be traced in the audit log.
  • Dates in ISO 8601, in universal time, everywhere.
  • A space is never given as a parameter: it is derived from the key. A resource belonging to another space is reported as not found.
GET /v1/contacts/ctc_…
Authorization: Bearer rt_live_…   # platform role

HTTP/1.1 404 Not Found
Content-Type: application/problem+json

{
  "type": "…/errors/not_found",
  "title": "Resource not found",
  "status": 404,
  "code": "not_found",
  "request_id": "req_3f2a…"
}

# A refusal says "not found", never
# "forbidden". It is written to the audit
# log; no read entry is, because there
# was no read.
What an idempotency key guarantees
  • The same call replayed with the same key returns the original response, identically, for twenty-four hours – without creating a second resource or triggering a second send.
  • The same call replayed with a different body is refused: the key is bound to the request, not merely to the intent.
  • Two concurrent calls carrying the same key produce a single effect; the second waits for the first one’s result.
  • After the window, the key is forgotten: a later reuse is a new request.

Vocabularies published as data

Workflows and dashboards follow the same rule: what an editor may offer is served by the contract, never guessed. GET /v1/workflow-node-types publishes the node catalogue with the JSON Schema of every configuration, generated from the very schemas the API validates against; POST /v1/workflows/{id}/compile compiles a definition passed in the body without storing anything and returns the plan, or the complete list of refusals with their position; GET /v1/stats/catalogue and POST /v1/stats/query form the closed-vocabulary aggregate query – no free expression, no contact field, no individual row; GET /v1/expression-functions serves the registry of expression functions, the same one the documentation is generated from.

Five calls to your first contact

The shortest path from a collection form to a first message on its way.

  1. Create a list – the entry point for contacts and the most common trigger.
  2. Create a template – the unsubscribe link is mandatory for marketing content; the sending domain must be verified.
  3. Create and activate a journey – send, wait, condition, send…
  4. Push the contact with its consent acts and its subscription, in a single call.
  5. Read its state – status, lists, enrolments, last event.
PUT /v1/contacts
Authorization: Bearer rt_live_…
Idempotency-Key: 7f1c…

{
  "email": "contact@example.com",
  "first_name": "Camille",
  "consents": [
    { "purpose": "marketing_email", "status": "granted",
      "at": "2026-09-19T14:32:00Z", "source": "form:landing",
      "proof_url": "https://proofs.example.com/abc" }
  ],
  "subscriptions": [{ "list_id": "lst_…" }]
}

→ 201  the contact enters the journey;
       the first message is queued within a minute.

Sandbox

A mode where nothing leaves, to integrate without risk.

  • Test keys are distinct and visibly prefixed; a test key never reaches production data.
  • Messages are captured instead of delivered, and remain readable for checking.
  • Bounces and complaints can be simulated, to test the journey branches that depend on them.
  • The same consent rules apply: a sandbox that allowed through what production refuses would be of no use.

Webhooks, events and exports

Two ways to find out what happens, and one common rule: no personal data on the way out.

  • Pull: the event log is read by API call, with filters and cursor pagination.
  • Push: signed HTTPS deliveries, with spaced retries and a log of attempts.
  • Protection against redirects to internal addresses: a subscription’s URL is checked before every call.
  • Payloads carry opaque identifiers only – never an e-mail address, a subject or a body.
  • The business events your tools post come back in segments, conditions and triggers.
  • Statistics and event-log exports, on demand or scheduled, as CSV or JSON, retrieved by download or pushed to a webhook.
What a delivery contains, and how it is verified
  • An event identifier, a type, a date in universal time and opaque identifiers of resources – nothing else.
  • A signature computed with a secret specific to the subscription, and a date in the header to reject replays.
  • The secret is returned only when the subscription is created; it can be rotated without interrupting deliveries.
  • Failed attempts are retried at increasing intervals, then abandoned, and each one can be inspected.
  • A missed event can be recovered from the event log: pull remains the source of truth.

Web console

The same platform, with screens – and exactly the same authorisations.

  • Contacts, lists, segments, consents; journeys, campaigns and service messages; templates, fragments, files and public pages; statistics and exports; domains, keys and retention settings.
  • The console is an API client like any other: it has no privileged access, and its navigation is built on the effective authorisation returned by the server.
  • Actions are attached to the person who triggered them, in addition to the key used, through a signed header that names without authorising.
  • Two-factor authentication for everyone; single sign-on through the customer’s identity provider is available.
  • No screen works around a consent rule: what the server refuses, the console does not get either.

Operations and deployment

Whatever is not automated ends up being done by hand, one evening, without a trace.

  • Deployment by artefact: a release is a verified build artefact – no history, no tests, no development tooling – published after a green continuous integration run and deployed as is. Rolling back means redeploying the previous release.
  • Blocking continuous integration: static analysis, type checking, a regenerated and compared contract, unit, integration and space-isolation tests, secret scanning, dependency audit, an actual start-up of the artefact.
  • Migrations deployed, not run: a schema change is code embedded in the release and executed at deployment. Nobody opens a session on production.
  • Separate pre-production environment, with a dedicated database and generated synthetic data, where every version is validated before production. No production data outside production: no copy, no restore elsewhere, no extract in an artefact.
  • No secrets in the repositories: start-up refuses an empty value, an example value, an insufficient length or two identical secrets.