Skip to main content
The API reads and writes. Reads work with any key; writes need a key with write access, and every write is attributed to a real member of your workspace rather than to “the API”.

Key scopes

Every API key has one of two scopes, chosen when you create it. Read only is the default, and it is the right choice for anything that only pulls data out — a reporting job, a warehouse sync, a dashboard. If that key ever leaks, the damage is disclosure rather than data loss.
Keys created before write access existed are read-only. Their behaviour has not changed. To write, create a new key with write access — you cannot upgrade an existing one.
A write sent with a read-only key returns 403:

Creating a write key

  1. In the Cockpit, go to Settings → API Keys.
  2. Click Create key and give it a name that says where it will be used — Zapier production, warehouse sync.
  3. Under Access, choose Read and write.
  4. Copy the key. It is shown once.
The list shows each key’s access alongside its name, so a write key is obvious at a glance.

How writes are attributed

An API key belongs to a workspace member — by default whoever created it. Every write made with that key is recorded as that person’s action:
  • Notes show their name as the author.
  • Logged calls appear on their activity.
  • Tasks are assigned to them unless you pass an assigneeId.
  • Timeline entries carry their user id.
This is deliberate. Work that arrives through an integration still has to be attributable to someone, or a manager looking at the timeline sees activity with no owner.
If the member a key belongs to is removed from the workspace, the key survives but loses its actor, and writes start returning 400. Reads keep working. Create a new key owned by a current member.

Creating leads

POST /leads needs a name plus at least one of email, phone or linkedinUrl. Without one of those there is no way to recognise the same person later, and every subsequent call would create another copy of them.

Retries are safe

Leads are deduplicated across the whole workspace by the person they resolve to. Post the same email twice and you get the same lead back, with created: false and a 200 instead of a 201:
That is why there are no idempotency keys to manage. The natural key is better than a header here, because it also catches the same person arriving from two different integrations — a form fill and a CSV import will not produce two Priyas. Check created when it matters. A webhook handler that emails “welcome” on every POST /leads will email the same person twice; one that emails only when created is true will not.

Workflows still fire

A lead created with a campaignId fires the Lead enters campaign trigger, exactly as one added in the Cockpit does. Sequences run, so an API-created lead is never a lead that quietly sits still. The same is true of PATCH /leads/{id}/status, which fires Status changed, and POST /campaigns/{id}/leads, which fires Lead enters campaign for each lead added.

Updating leads

PATCH /leads/{id} touches only the keys you send. Omitting a field leaves it alone; sending null clears it. This is what makes a partial sync from another system safe — you can push the two fields you own without flattening everything else.
Custom fields work the same way on POST /leads, so a lead can arrive with them already set rather than needing a second call. A key that doesn’t exist yet is created automatically, which means a migration doesn’t have to pre-declare its columns in Settings first. That makes custom fields the right place to park identifiers from a system you’re moving off — the original record id, its created date — so you can find your rows again afterwards:
Keys are slugified, so Legacy Record ID is stored and read back as legacy_record_id. The response echoes the canonical keys, and GET /leads and GET /leads/{id} return customFields in exactly the shape you wrote — so a value round-trips unchanged. Values are stored as text; numbers, dates and booleans are coerced rather than rejected. Status is the one field this endpoint won’t take. Use PATCH /leads/{id}/status: it validates the key against your workspace’s list, records the transition on the timeline, and fires the Status changed trigger. Get the valid keys from GET /lead-statuses — an unknown one returns 400 rather than storing a status nothing in the product recognises.

Stopping contact

To stop contacting someone, flag them rather than deleting them:
The flag applies to the person, not the row: it follows them across every lead in the workspace that resolves to the same human, so one call cannot leave a duplicate un-suppressed. The response’s flaggedLeads tells you how many rows were affected. They stay in their campaigns, and their history stays intact. DELETE /leads/{id} exists, but it is permanent and takes the history with it. Reach for it only when a record should never have existed.

Notes and calls

Notes are plain text and appear on the lead’s timeline immediately:
POST /calls records a call that happened somewhere else — a mobile, another dialler, a conference line — so the lead’s history and your call metrics stay complete. Pass duration in seconds and it counts towards your talk time; notes becomes the call’s summary.
Calls placed through Leadey are recorded automatically. Do not post them here as well, or they are counted twice in your metrics.
Outcome keys come from GET /call-outcomes.

Tasks

GET /tasks returns open tasks by default, soonest-due first. An integration polling for work to do wants the outstanding list, so completed tasks are excluded until you ask for them with done=true or done=all.
New tasks default to the key’s member as assignee — an unassigned task is invisible work. Complete one with PATCH /tasks/{id} and { "done": true } rather than deleting it, so it stays on the record.

When writes are blocked

A workspace that cannot write in the Cockpit cannot write through the API either. If the trial has expired, an invoice is long overdue, or the subscription has been cancelled, writes return 403 with a code you can branch on:
Reads keep working throughout, so a reporting integration is never taken down by a billing problem.

A full round trip

Creating a lead, working it, and reading back everything that happened:
Every one of those actions shows up on the timeline, attributed to the member the key belongs to.

Rate limits

No per-key rate limit is enforced today, on writes or reads. One is coming, with headers and notice — see Rate limits for what to expect and how to build for it now.