Skip to main content
Every API call runs the same database function the app runs for that action. The API adds nothing on top and skips nothing.
Daily caps, schedules, warm-up, sender health, reply-stop and blacklists are enforced in the database. The API cannot send more than the app can, and there is no flag to turn a cap off. Nothing is deleted through the API either: suppressing a lead or exiting an enrollment keeps the lead, its timeline and its chats.

Keys

Create them under Settings → API & webhooks → API keys (Managers). A key is shown once — we store only its hash, so a lost key cannot be recovered. Revoke it and make a new one. A workspace can have 25 active keys.
X-API-Key: ok_live_… works too, for tools that can’t set an Authorization header. A key acts as the member who created it, and audit entries name that member. If they leave the workspace or their role is lowered, the key loses the same rights immediately. GET /v1/me returns the key’s role and scope, plus the ids you need elsewhere: clients, stages, tags and lists.

Routes

Building and publishing sequences, connecting senders and changing caps are not in the API. Do those in the app or through Claude.

Rate limits

Per key, per hour: Responses carry X-RateLimit-Limit, X-RateLimit-Remaining and X-RateLimit-Reset. A 429 carries Retry-After. These protect the API — they are not your sending limits. For bulk work, send many ids in one call (lead_ids takes up to 10,000 on enrollments, 5,000 on enrich) rather than one call per lead.

Idempotency

Send an Idempotency-Key header on any write:
  • Same key, same request → the first answer again, with Idempotent-Replayed: true. Nothing runs twice.
  • Same key, different request → 409 E_IDEMPOTENCY_MISMATCH.
  • Same key while the first is still running → 409 E_IN_PROGRESS.
  • Answers below 500 are stored, including 4xx. A 5xx is not, because a failed call changed nothing — retry with the same key.
Zapier, Make and n8n retry on timeouts, so set this header on every write in a recipe.

Partial updates

POST /v1/leads and PATCH /v1/leads/{id} change only the fields you send.
  • A field you leave out is never blanked. An empty string or null counts as left out.
  • custom merges key by key.
  • email_work, email_personal, public_identifier and client_id are filled only when empty and never overwritten. Send a different value and the call succeeds, the old value stays, and the field appears in unchanged_fields.
  • Unknown fields are rejected with 422, so a typo can’t be silently dropped.
POST /v1/leads matches on LinkedIn id, then provider id, then work email, then personal email. No match creates the lead (201), a match updates it (200).

Preview, then commit

Enrolling takes two steps, the same as in the app:
  1. POST /v1/enrollments/preview — changes nothing. Returns eligible_ids, who is excluded and why, which sender each lead would get, warnings and a duration estimate.
  2. POST /v1/enrollments with the same sequence, the lead_ids you want and "confirm": true.
The commit re-checks every rule at that moment. It can enrol fewer leads than the preview showed, never a lead the rules exclude, and never a lead you did not list.

Errors

Follow the remedy. The full reference, with every route and schema, is in the OpenAPI file — import it into Postman, Insomnia or Swagger UI.