SuperCoolDocs Get an API key

Guides

CRM

The CRM API behind the SuperCool Zapier app. Contacts, tags, opportunities, tasks, workflows, texts and emails, REST hook triggers, and the Lead API.

View as Markdown

The SuperCool CRM has its own API. The SuperCool app on Zapier is built on it, and your code can call it too: create and update contacts, tag them, open and move opportunities, add tasks, put contacts into workflows, send texts and emails, subscribe to CRM events, and post leads from your own forms.

Details
Base URL https://api.supercool.com/api/v1/crm-app
Auth Authorization: Bearer sc_key_…, plus X-Workspace-Id for a team's CRM (Authentication)
Format JSON requests and responses
Errors {"detail": …} with a matching HTTP status (Errors)
Rate limits 300 requests per minute per key on /zapier/…, 600 leads per minute per key on /leads (Rate limits)

These endpoints are not under /v1. They use the same API keys as the rest of the SuperCool API, but their own base URL and error shape. They're also in the API reference and the OpenAPI spec.

#Authentication

Send one of your dashboard API keys as a bearer token:

http
Authorization: Bearer sc_key_9fK2...x7Qa
X-Workspace-Id: 6650a1b2c3d4e5f6a7b8c9d0
  • Use a regular key. An isolated key gets 403.
  • Pick the CRM. Without X-Workspace-Id, requests go to the key owner's personal CRM. To use a team's CRM, send the team id in X-Workspace-Id. The key's owner must be a member of that team (any role). The team id is shown on the Zapier card in the CRM's Settings → Integrations. The /zapier/… endpoints also accept it as ?workspace_id=, and POST /leads as workspace_id in the body; the header wins.
  • Everything is done as the key. Contacts, tags, opportunities and tasks it creates, and the activity it writes on contacts, are attributed to the key (api_key:<key id>).

#Test a key

GET /zapier/me checks the key and returns who it belongs to and which CRM it reaches. Zapier calls it when you connect an account and shows label as the connection's name.

bash
curl https://api.supercool.com/api/v1/crm-app/zapier/me \
  -H "Authorization: Bearer $SUPERCOOL_API_KEY" \
  -H "X-Workspace-Id: 6650a1b2c3d4e5f6a7b8c9d0"
json
{
  "ok": true,
  "user_id": "64f1c2a9e4b0a1b2c3d4e5f6",
  "email": "[email protected]",
  "workspace_id": "6650a1b2c3d4e5f6a7b8c9d0",
  "team_name": "Rivera Studio",
  "role": "admin",
  "key": { "id": "pat:6a1b2c3d4e5f", "name": "Zapier", "hint": "sc_key_9fK2…x7Qa" },
  "label": "[email protected] · Rivera Studio"
}

workspace_id is the team id (null for a personal CRM), team_name the team's name, and role your role in that CRM: admin or member.

Status detail Cause
401 Invalid API key The key is missing, unknown, expired or revoked. The response has a WWW-Authenticate: Bearer header.
403 An isolated API key cannot use the CRM; use a regular key An isolated key. On POST /leads: An isolated API key cannot post leads; use a regular key.
403 The key's owner is not a member of that team X-Workspace-Id names a team the key's owner doesn't belong to.

#Errors

Errors have one field, detail. It's usually a sentence:

json
{ "detail": "Contact not found" }
Status When
400 The input can't be used: no email or phone, an invalid email or phone number, an unknown trigger, a target_url that isn't a public URL, an unknown pipeline or stage, a contact that already has an open opportunity in that pipeline, an email address another contact already has, nothing to update. detail says which.
401 The API key is missing, unknown, expired or revoked.
403 An isolated key, or a team the key's owner isn't a member of.
404 The contact, opportunity, workflow or subscription isn't in this CRM.
422 The body doesn't match the endpoint's schema (a required field is missing, or a field has the wrong type), or a text or email was refused by the CRM's rules. See below.
429 Too many requests for this key. See Rate limits.
503 The feature isn't available on this account yet (detail names it, for example Pipelines is not available in this build yet).

There is no 409: conflicts, like a duplicate opportunity or an email address that's already taken, are 400. An unexpected failure is 500 with {"detail": "Something went wrong"}.

A 422 has one of two shapes. A schema error lists the problems:

json
{ "detail": [{ "type": "missing", "loc": ["body", "title"], "msg": "Field required", "input": {} }] }

A refused text or email has a machine-readable reason and a sentence you can show people:

json
{ "detail": { "reason": "no_consent", "message": "Text campaigns go only to contacts with explicit SMS consent." } }

See refusal reasons for every reason.

#Rate limits

Limit Value Applies to
Zapier endpoints 300 per minute Each API key, every /zapier/… request
Lead API 600 per minute Each API key, POST /leads

Over the limit, the request gets 429 with {"detail": "Too many Zapier requests per minute for this key"} (or Too many leads per minute for this key). The window is a rolling 60 seconds: wait, then retry. Texts and emails also have daily sending limits; going over them is a refusal, not a 429.

#Contacts in requests

Most actions work on one existing contact. Name it with any of these fields:

Field Meaning
contact_id The contact's id (ct_…).
contact One field that takes an id, an email address or a phone number.
email The contact's email address.
phone The contact's phone number.

The contact is found by contact_id (or contact) as an id, then as an email address (if it contains @) or a phone number, then by email, then by phone. If none matches, the request gets 404 with Contact not found.

#The contact object

Contact actions return the contact. The main fields:

Field Type Description
id string The contact's id (ct_…).
email string or null Email address.
phone string or null Phone number in E.164 (+15555550123).
first_name, last_name, company string or null Name and company.
address, city, state, postal_code, country, website string or null Address and website, when set.
display_name string The full name, else the email, else the phone.
tags string[] Tag ids (tag_…). GET /zapier/tags maps them to names.
fields object Custom field values by key. GET /zapier/fields lists the keys.
source string Where the contact came from: zapier for contacts this API created, lead_api for the Lead API, and others such as form, import or manual.
owner_user_id string or null The team member who owns the contact.
status string active, unsubscribed, bounced or blocked.
dnd object Do Not Disturb: email, sms, calls, all (booleans), reason, at.
consent object email and call, each {status, source, at}. Email consent explicit is what Send Email needs.
created_at, updated_at, last_activity_at string ISO 8601 UTC.

Other fields may be present, such as attribution (first and last touch from forms and the Lead API).

#Contacts

#Create or update a contact

POST /zapier/contacts finds the contact by email, then by phone, and updates it, or creates it if there is none. A new contact's source is zapier. Only the non-empty fields you send are written; nothing is blanked.

Field Type Description
email string Email address. email or phone is required.
phone string Phone number. US numbers without a country code get +1.
first_name, last_name, company, address, city, state, postal_code, country, website string Contact details.
owner_user_id string A team member's user id (GET /zapier/team).
tags string[] or string Tag names to add (a list, or one comma-separated string; up to 50). Missing tags are created.
fields object Custom field values by key. Unknown keys are ignored.
consent boolean true records explicit email consent (source zapier). Otherwise email consent is recorded as imported_unknown, unless the contact's is already explicit.
sms_consent boolean true records explicit SMS consent (source zapier), if the contact has a phone number.

Explicit consent is never downgraded. Send consent and sms_consent only when the person actually agreed.

bash
curl https://api.supercool.com/api/v1/crm-app/zapier/contacts \
  -H "Authorization: Bearer $SUPERCOOL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "email": "[email protected]",
    "first_name": "Jane",
    "last_name": "Doe",
    "phone": "(555) 555-0123",
    "tags": "lead, webinar",
    "consent": true
  }'

Returns 200 with the contact at the top level, plus ok and created (true if the contact is new):

json
{
  "ok": true,
  "created": true,
  "id": "ct_8f2a6c1e9b3d5f7a0c4e",
  "email": "[email protected]",
  "phone": "+15555550123",
  "first_name": "Jane",
  "last_name": "Doe",
  "display_name": "Jane Doe",
  "tags": ["tag_1a2b3c4d5e6f7a8b9c0d", "tag_0d9c8b7a6f5e4d3c2b1a"],
  "fields": {},
  "source": "zapier",
  "status": "active",
  "dnd": { "email": false, "sms": false, "calls": false, "all": false, "reason": null, "at": null },
  "consent": {
    "email": { "status": "explicit", "source": "zapier", "at": "2026-10-23T14:02:11.402000Z" },
    "call": { "status": "none", "source": null, "at": null }
  },
  "created_at": "2026-10-23T14:02:11.402000Z",
  "updated_at": "2026-10-23T14:02:11.402000Z"
}

Errors: 400 (email or phone is required, Email address is not valid, Phone number is not valid, a custom field value of the wrong type), 422 (wrong field types).

#Update a contact

POST /zapier/contacts/update changes an existing contact. Name the contact with the lookup fields, and send the fields to change: the same contact fields as create or update. Only the fields you send change. tags are added to the contact's tags. Custom field keys must exist. consent and sms_consent are ignored here.

An email or phone that you used to find the contact, and that it already has, is not a change.

bash
curl https://api.supercool.com/api/v1/crm-app/zapier/contacts/update \
  -H "Authorization: Bearer $SUPERCOOL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "contact": "[email protected]", "company": "Example Co", "tags": ["customer"] }'

Returns 200 with ok: true and the updated contact at the top level.

Errors: 400 (Nothing to update, Another contact already has that email, Unknown custom field '…', invalid email or phone), 404 (Contact not found).

#Find a contact

GET /zapier/contacts/find looks a contact up by id, contact, email or phone (query parameters, same order as the lookup fields). It never returns 404: no match is an empty list.

bash
curl "https://api.supercool.com/api/v1/crm-app/zapier/contacts/[email protected]" \
  -H "Authorization: Bearer $SUPERCOOL_API_KEY"
json
{ "items": [{ "id": "ct_8f2a6c1e9b3d5f7a0c4e", "email": "[email protected]", "first_name": "Jane", "last_name": "Doe" }] }

Each item is a full contact (shortened here).

#Add tags

POST /zapier/contacts/tags adds tags to a contact, by name. Missing tags are created. Name the contact with the lookup fields.

Field Type Description
tags string[] or string Required. Tag names (a list or one comma-separated string; up to 50, 80 characters each).
bash
curl https://api.supercool.com/api/v1/crm-app/zapier/contacts/tags \
  -H "Authorization: Bearer $SUPERCOOL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "contact_id": "ct_8f2a6c1e9b3d5f7a0c4e", "tags": ["vip", "webinar"] }'

Returns 200 with ok: true and the contact. Adding tags the contact already has changes nothing. Errors: 400 (tags is required), 404 (Contact not found).

#Remove tags

POST /zapier/contacts/tags/remove takes tags off a contact. tags takes names (matched ignoring case and extra spaces) or tag ids (tag_…). Tags the contact doesn't have are skipped.

bash
curl https://api.supercool.com/api/v1/crm-app/zapier/contacts/tags/remove \
  -H "Authorization: Bearer $SUPERCOOL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "email": "[email protected]", "tags": "webinar" }'

Returns 200 with ok: true and the contact. Errors: 400 (tags is required), 404 (Contact not found).

#Opportunities

An opportunity is a potential sale: one contact, in one stage of one pipeline, with a status of open, won, lost or abandoned.

#Create an opportunity

POST /zapier/opportunities opens an opportunity for a contact. Name the contact with the lookup fields.

Field Type Description
pipeline_id or pipeline string The pipeline, by id (pl_…) or name. Neither: the default pipeline.
stage_id or stage string The stage, by id (st_…) or name (any case). Neither: the pipeline's first stage.
name string Defaults to the contact's name.
value number or string A number is in minor units (cents): 150000 is 1,500.00. A string is in major units: "1,500.00" is 1,500.00.
currency string 3-letter code. Defaults to the CRM's currency.
owner_user_id string A team member's user id. Ignored if they're not on the team.
tags string[] or string Tag names. Missing tags are created.
fields object Opportunity custom field values by key.
expected_close_at string A date, YYYY-MM-DD.
allow_duplicate boolean A contact can have one open opportunity per pipeline. true allows another.
bash
curl https://api.supercool.com/api/v1/crm-app/zapier/opportunities \
  -H "Authorization: Bearer $SUPERCOOL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "email": "[email protected]", "pipeline": "Sales", "stage": "Qualified", "value": "1500" }'

Returns 201 with the opportunity:

json
{
  "id": "opp_7d8e9f0a1b2c3d4e5f6a",
  "pipeline_id": "pl_2b3c4d5e6f7a8b9c0d1e",
  "stage_id": "st_5e6f7a8b9c0d1e2f3a4b",
  "contact_id": "ct_8f2a6c1e9b3d5f7a0c4e",
  "name": "Jane Doe",
  "status": "open",
  "value": 150000,
  "currency": "usd",
  "source": "zapier",
  "owner_user_id": null,
  "tags": [],
  "fields": {},
  "expected_close_at": null,
  "created_at": "2026-10-23T14:05:00.118000Z",
  "updated_at": "2026-10-23T14:05:00.118000Z"
}

Other fields, such as followers, lost_reason, stage_entered_at and days_in_stage, are included too. value is always in minor units in responses. Responses from these endpoints don't fill in pipeline_name and stage_name; use GET /zapier/pipelines to name the ids.

Errors: 400 (Pipeline not found, No stage '…' in pipeline '…', Jane Doe already has an open opportunity in Sales, Value must be a number), 404 (Contact not found), 503 (pipelines aren't available on the account).

#Move an opportunity

POST /zapier/opportunities/{opportunity_id}/move moves an opportunity to another stage, another pipeline, or another status. Send at least one of:

Field Type Description
stage_id or stage string The new stage, by id or name, in the opportunity's pipeline (or in the new pipeline).
pipeline_id or pipeline string Move it to this pipeline (by id or name). Without a stage, it goes to that pipeline's first stage.
status string open, won, lost or abandoned.
lost_reason string With status: "lost".
bash
curl https://api.supercool.com/api/v1/crm-app/zapier/opportunities/opp_7d8e9f0a1b2c3d4e5f6a/move \
  -H "Authorization: Bearer $SUPERCOOL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "stage": "Proposal sent" }'

Returns 200 with the opportunity. Errors: 400 (stage, pipeline or status is required, an unknown stage or pipeline, Status must be open, won, lost or abandoned, the contact already has an open opportunity in the new pipeline), 404 (Opportunity not found), 503.

#Tasks

#Create a task

POST /zapier/tasks adds a task, optionally on a contact.

Field Type Description
title string Required. Longer titles are cut to 200 characters.
description string Details.
contact_id, contact, email, phone string Optional. The task's contact, found like the lookup fields. If you send one and it doesn't match, the request gets 404.
opportunity_id string Link the task to an opportunity.
assigned_to string A team member's user id (GET /zapier/team).
due_at string An exact ISO 8601 time. Wins over due_date.
due_date string YYYY-MM-DD. Without due_time, the task is due all day.
due_time string HH:MM, with due_date.
timezone string The IANA zone due_date and due_time are in, like America/Chicago. Defaults to the assignee's zone, else the CRM's, else UTC.
bash
curl https://api.supercool.com/api/v1/crm-app/zapier/tasks \
  -H "Authorization: Bearer $SUPERCOOL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "title": "Call Jane", "email": "[email protected]", "due_date": "2026-10-27", "due_time": "10:00", "timezone": "America/Chicago" }'

Returns 201 with the task: id (tsk_…), title, description_html, contact_id, opportunity_id, assigned_to, due_at, due_date, due_time, all_day, timezone, status (open), origin ({"kind": "manual", "reason": "Created by Zapier"}), created_at and updated_at.

Errors: 400 (title is required, due_date must be YYYY-MM-DD, due_time must be HH:MM, unknown time zone '…'), 404 (Contact not found), 422 (no title field), 503.

#Workflows

#Add a contact to a workflow

POST /zapier/workflows/{workflow_id}/enroll starts a published workflow for a contact. Name the contact with the lookup fields. The workflow's own re-entry rules apply.

bash
curl https://api.supercool.com/api/v1/crm-app/zapier/workflows/wf_0a1b2c3d4e5f6a7b8c9d/enroll \
  -H "Authorization: Bearer $SUPERCOOL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "email": "[email protected]" }'
json
{
  "ok": true,
  "reason": "enrolled",
  "enrollment_id": "wfe_6f7a8b9c0d1e2f3a4b5c",
  "workflow_id": "wf_0a1b2c3d4e5f6a7b8c9d",
  "workflow_name": "New lead follow-up",
  "contact_id": "ct_8f2a6c1e9b3d5f7a0c4e"
}

Not being enrolled is not an error: the response is 200 with ok: false and the reason:

reason Meaning
enrolled The contact is in the workflow (ok: true).
already_active The contact is already going through this workflow.
no_reentry The contact went through it before, and the workflow doesn't allow re-entry.
not_published The workflow isn't published.

Errors: 404 (Workflow not found, Contact not found), 503.

#Texts and emails

Send Text and Send Email are marketing sends. They follow the same rules as a campaign, so they only reach people who agreed to hear from you:

  • Email needs explicit email consent. The contact's email consent must be explicit. The API sets it with consent: true on create or update or the Lead API. Contacts created without it get imported_unknown, which isn't enough.
  • Texts need explicit SMS consent. The contact's SMS consent must be explicit. The API sets it with sms_consent: true, only for a contact with a phone number. Nothing else the API does grants SMS consent.
  • Opt-outs always win. A contact on Do Not Disturb, an address that unsubscribed, bounced or complained, and a number that replied STOP are never sent to.
  • Texts respect quiet hours. Texts go out only during the CRM's texting hours (8:00 to 21:00 by default) in the recipient's time zone, worked out from the number. Outside them, the send is refused with quiet_hours.
  • Daily limits apply, and so do pauses on the account (for example after a high bounce rate).

A refused send gets 422 with a reason (see below). Nothing is sent and nothing is queued: fix the cause, or try again later.

Texts and emails use credits. The response's credits is what the send cost.

#Send a text

POST /zapier/messages/sms texts a contact from the CRM's phone number. Name the contact with the lookup fields.

Field Type Description
text (or message) string Required. The text. Longer than 1,600 characters is cut; more than 10 segments, including the opt-out footer the CRM adds when it's needed, is refused with too_long.
media string[] Optional. One image file already stored in SuperCool (its storage key), up to 1 MB, to send as MMS. Anything else, including a public URL, is refused with media.
bash
curl https://api.supercool.com/api/v1/crm-app/zapier/messages/sms \
  -H "Authorization: Bearer $SUPERCOOL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "contact": "+15555550123", "text": "Hi Jane, your order shipped today." }'
json
{
  "ok": true,
  "contact_id": "ct_8f2a6c1e9b3d5f7a0c4e",
  "id": "cmsg_9b2c4d6e8f0a1b3c5d7e",
  "status": "accepted",
  "kind": "sms",
  "conversation_id": "cv_3c4d5e6f7a8b9c0d1e2f",
  "segments": 1,
  "credits": 10,
  "error": null,
  "created_at": "2026-10-23T14:10:02.551000Z"
}

status is accepted (handed to the carrier), uncertain (the carrier's answer didn't come back), failed, or refused (the carrier says the number opted out). Errors: 400 (text is required), 404 (Contact not found), 422 (refused), 503.

#Send an email

POST /zapier/messages/email emails a contact from the CRM's default sender. Name the contact with the lookup fields.

Field Type Description
subject string Required. Longer subjects are cut to 300 characters.
html (or body) string The HTML body. html, body or text is required.
text string A plain-text body. Without html, it's also turned into the HTML body.
bash
curl https://api.supercool.com/api/v1/crm-app/zapier/messages/email \
  -H "Authorization: Bearer $SUPERCOOL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "email": "[email protected]", "subject": "Your order shipped", "text": "Hi Jane,\nyour order shipped today." }'

Returns 201 with ok, contact_id and the message (id, status, kind, conversation_id, credits, error, created_at). status is accepted, uncertain or failed. Errors: 400 (subject and html (or text) are required), 404 (Contact not found), 422 (refused), 503.

#Refusal reasons

reason Text Email Meaning
no_consent ✓ ✓ No explicit consent for this channel. See consent rules.
dnd ✓ ✓ The contact is on Do Not Disturb.
suppressed_owner ✓ ✓ The number replied STOP, or the address unsubscribed from your emails.
suppressed_global ✓ ✓ The number or address can't receive messages (a permanent carrier rejection, a bounce or a complaint).
paused ✓ ✓ Sending is paused on the account.
cap ✓ ✓ Today's sending limit is reached.
credits ✓ ✓ Not enough credits.
no_phone ✓ The contact has no phone number.
landline ✓ The number is a landline.
quiet_hours ✓ Outside texting hours for the recipient.
no_number ✓ The CRM has no phone number to text from.
unregistered ✓ The number's carrier registration isn't approved yet.
suspended, releasing, unpaid ✓ The number is suspended, being released, or its monthly charge didn't go through.
kill_switch ✓ Texting is paused platform-wide.
too_long ✓ The text is empty or longer than 10 segments.
media ✓ The attachment isn't usable: one image file stored in SuperCool, up to 1 MB.
no_email ✓ The contact has no email address.
no_channel ✓ Email isn't set up yet (no From name and address).
unsubscribed, bounced ✓ The contact unsubscribed, or their address bounced.

#Lists for dropdowns

These return what the Zapier app shows in its dropdowns, as {"items": [...]}:

Endpoint Items
GET /zapier/pipelines {id, name, stages: [{id, name, position}]}, stages in order. 503 if pipelines aren't available.
GET /zapier/workflows {id, name, status}, published workflows only. 503 if workflows aren't available.
GET /zapier/tags {id, name}
GET /zapier/team {id, name, email, role}: the team's members (or just you, in a personal CRM).
GET /zapier/fields {key, label, type}: the contact custom fields.
GET /zapier/triggers {key, label, noun, types}: the triggers and the CRM event types each fires on.
bash
curl https://api.supercool.com/api/v1/crm-app/zapier/pipelines \
  -H "Authorization: Bearer $SUPERCOOL_API_KEY"
json
{
  "items": [{
    "id": "pl_2b3c4d5e6f7a8b9c0d1e",
    "name": "Sales",
    "stages": [
      { "id": "st_4d5e6f7a8b9c0d1e2f3a", "name": "New lead", "position": 0 },
      { "id": "st_5e6f7a8b9c0d1e2f3a4b", "name": "Qualified", "position": 1 }
    ]
  }]
}

#Triggers (REST hooks)

To hear about CRM events, subscribe a URL to a trigger. SuperCool POSTs each matching event to it, as it happens. This is how Zapier's instant triggers work, and it works the same for any URL.

#Trigger catalogue

Trigger key Fires when data has
new_contact A contact is created (contact.created). source, tags (ids), owner_user_id
contact_updated A contact's fields change (contact.updated). changed (field names), source
tag_added A tag is added to a contact (contact.tag_added). tag_id, tag_name
tag_removed A tag is removed from a contact (contact.tag_removed). tag_id, tag_name
form_submitted A CRM form is submitted, or a lead is posted to the Lead API (form.submitted). Forms: form_id, form_name, source (form), created, fields, page_url, referrer, utm. Lead API: source (lead_api), lead_source, key_id, created, tags, fields.
lead_received A Facebook lead form is submitted (facebook.lead_received), or a lead is posted to the Lead API. Facebook: form_id, form_name, page_id, ad_id, adset_id, campaign_id, leadgen_id, is_test, is_organic and the answers. Lead API: as above.
appointment_booked An appointment is booked (appointment.booked). calendar_id, calendar_name, start, end, timezone, assigned_user_id, status, source
appointment_changed An appointment is rescheduled, cancelled or confirmed, or marked showed or no-show (appointment.rescheduled, .cancelled, .confirmed, .showed, .no_show). As appointment_booked. A reschedule adds previous_start and previous_end. type says which change.
opportunity_created An opportunity is created (opportunity.created). name, pipeline_id, pipeline_name, stage_id, stage_name, status, value (minor units), currency, owner_user_id, source, tags, expected_close_at
opportunity_stage_changed An opportunity moves to another stage (opportunity.stage_changed). As opportunity_created, plus previous_stage_id and previous_stage_name
payment_received A payment is received (payment.received). source (calendar, site or stripe), amount (minor units), currency, and details from the source
task_added A task is added (task.added). title, due_at, due_date, all_day, assignee_id, origin, contact_id, opportunity_id, recurring
task_completed A task is completed (task.completed). As task_added
inbound_webhook One of the CRM's inbound webhooks receives a request (webhook.received). webhook_id, webhook_name, method, payload (the body, flattened), mapped, created

A Lead API post fires both form_submitted and lead_received, and new_contact or contact_updated as well.

#Subscribe

POST /zapier/hooks subscribes a URL to one trigger.

Field Type Description
event string Required. A trigger key.
target_url string Required. A public http or https URL, up to 2,048 characters. Private and local addresses are refused.
bash
curl https://api.supercool.com/api/v1/crm-app/zapier/hooks \
  -H "Authorization: Bearer $SUPERCOOL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "event": "new_contact", "target_url": "https://hooks.example.com/supercool/new-contact" }'

Returns 201 with the subscription:

json
{
  "id": "zh_4c1d2e3f4a5b6c7d8e9f",
  "key_id": "pat:6a1b2c3d4e5f",
  "event": "new_contact",
  "label": "New Contact",
  "target_url": "https://hooks.example.com/supercool/new-contact",
  "target_host": "hooks.example.com",
  "key_name": "Zapier",
  "key_hint": "sc_key_9fK2…x7Qa",
  "deliveries": 0,
  "failures": 0,
  "last_delivery_at": null,
  "last_status": null,
  "last_error": null,
  "created_at": "2026-10-23T13:40:00Z",
  "updated_at": "2026-10-23T13:40:00Z"
}
  • Subscribing the same URL to the same trigger again returns the existing subscription, so retrying a subscribe is safe.
  • A CRM can have up to 100 subscriptions. Past that, the request gets 400.
  • Subscriptions belong to the CRM, not to the key: every key that reaches the CRM sees and can remove them, and they're listed on the Zapier card in Settings → Integrations, where an admin can remove them too.

Errors: 400 (Unknown event '…'; one of: …, target_url must be a public http(s) URL, the 100-subscription limit), 422 (a missing field).

#List subscriptions

GET /zapier/hooks returns the CRM's subscriptions, newest first, as {"items": [...]}. deliveries and failures count delivered and failed events; last_status and last_error describe the latest attempt.

bash
curl https://api.supercool.com/api/v1/crm-app/zapier/hooks -H "Authorization: Bearer $SUPERCOOL_API_KEY"

#Unsubscribe

DELETE /zapier/hooks/{hook_id} removes one subscription. Events still waiting to be delivered to it are dropped.

bash
curl -X DELETE https://api.supercool.com/api/v1/crm-app/zapier/hooks/zh_4c1d2e3f4a5b6c7d8e9f \
  -H "Authorization: Bearer $SUPERCOOL_API_KEY"
json
{ "ok": true }

DELETE /zapier/hooks with a JSON body does the same by id, or by target_url (every subscription to that URL, or only the one for event if you send it). It needs id or target_url (400 otherwise).

bash
curl -X DELETE https://api.supercool.com/api/v1/crm-app/zapier/hooks \
  -H "Authorization: Bearer $SUPERCOOL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "target_url": "https://hooks.example.com/supercool/new-contact", "event": "new_contact" }'

Both return 404 (Hook not found) when nothing matches.

#Sample events

GET /zapier/perform-list/{event} returns up to 3 of the CRM's most recent events for a trigger, newest first, in the same payload a subscription receives. When the CRM has none yet, it returns one made-up sample ("id": "ev_sample") so you can map fields before the first real event. An unknown trigger key gets 400.

bash
curl https://api.supercool.com/api/v1/crm-app/zapier/perform-list/new_contact \
  -H "Authorization: Bearer $SUPERCOOL_API_KEY"

#Payload

Every delivery is a JSON POST of one event:

json
{
  "id": "ev_3f9a1c2e7b8d4e5f6a7b",
  "trigger": "new_contact",
  "type": "contact.created",
  "label": "Contact created",
  "occurred_at": "2026-10-23T14:02:11.402000Z",
  "contact_id": "ct_8f2a6c1e9b3d5f7a0c4e",
  "contact": {
    "id": "ct_8f2a6c1e9b3d5f7a0c4e",
    "email": "[email protected]",
    "phone": "+15555550123",
    "first_name": "Jane",
    "last_name": "Doe",
    "full_name": "Jane Doe",
    "company": "Example Co",
    "address": null,
    "city": "Austin",
    "state": "TX",
    "postal_code": "78701",
    "country": "US",
    "website": null,
    "source": "form",
    "owner_user_id": null,
    "fields": {},
    "tag_ids": ["tag_1a2b3c4d5e6f7a8b9c0d"],
    "tags": ["lead"],
    "dnd": { "email": false, "sms": false, "calls": false, "all": false, "reason": null, "at": null },
    "consent": { "email": "explicit", "sms": null },
    "created_at": "2026-10-23T14:02:11.402000Z",
    "updated_at": "2026-10-23T14:02:11.402000Z"
  },
  "data": { "source": "form", "tags": ["tag_1a2b3c4d5e6f7a8b9c0d"], "owner_user_id": null },
  "ref": {},
  "actor": { "kind": "system", "id": null }
}
Field Description
id The event's id (ev_…). The same on every retry: use it to drop duplicates.
trigger The trigger key the subscription is for.
type, label The CRM event type and its name.
occurred_at When it happened (ISO 8601 UTC).
contact_id, contact The contact it's about, or null. contact.tags are names and tag_ids their ids; consent gives the email and SMS consent status.
data What happened. Its fields depend on the trigger: see the catalogue.
ref Ids of related records, such as opportunity_id, appointment_id, task_id or form_id.
actor Who did it: {kind, id}, where kind is user, api_key, webhook or system.

#Delivery

The request carries these headers:

Header Value
Content-Type application/json
User-Agent SuperCool-CRM-Zapier/1.0
X-SuperCool-Event The CRM event type, such as contact.created.
X-SuperCool-Delivery This delivery's id (zhd_…). The same on every retry.

Deliveries are not signed. Treat the target_url as a secret, and use a URL that's hard to guess.

  • Respond with any 2xx within 10 seconds. Redirects are not followed.
  • 410 Gone unsubscribes. The subscription is removed, with anything still waiting for it. This is how Zapier turns a hook off when a Zap is deleted.
  • Some failures are retried. A timeout, a network error, 408, 425, 429 or any 5xx is retried after 1 minute, 5 minutes, 15 minutes, 1 hour, 6 hours and 24 hours: up to 7 attempts in all. Then the delivery is marked failed.
  • Other 4xx (and 3xx) responses fail at once, without retries.
  • Delivery is at least once. Each event gets one delivery per subscription, but that delivery can reach you more than once: for example, when your server handled it but answered after 10 seconds, it's retried. Drop duplicates by id. Events can arrive out of order.
  • A revoked key stops its subscriptions. If the API key that created a subscription is revoked, the subscription is removed the next time an event would go to it.

Failures show on the subscription (failures, last_status, last_error) and on the Zapier card in Settings → Integrations.

#Lead API

POST /leads adds a lead to the CRM from your own form, landing page or backend. It creates the contact, or updates the one with the same email or phone, with source lead_api. It's like create or update, plus attribution and an optional opportunity.

Field Type Description
email string Email address. email or phone is required.
phone string Phone number.
first_name, last_name, company, address, city, state, postal_code, country, website string Contact details.
tags string[] Tag names to add (up to 50). Missing tags are created. Must be a list.
fields object Custom field values by key. Unknown keys are ignored.
consent boolean true records explicit email consent (source lead_api). Otherwise email consent is imported_unknown, unless it's already explicit.
sms_consent boolean true records explicit SMS consent, if the lead has a phone number. The only way the Lead API grants SMS consent.
source string Your label for where the lead came from, like "pricing page". Kept on the contact's activity and the event as lead_source.
attribution object Optional: utm_source, utm_medium, utm_campaign, utm_term, utm_content, gclid, fbclid, referrer, landing_url. The first attributed lead becomes the contact's first touch; every one updates the last touch.
opportunity object Optional: add the lead to a pipeline. pipeline_id or pipeline (name), stage_id (default: the first stage), value (minor units if a number, major units if a string), name (default: the contact's name). If the opportunity can't be created (an unknown pipeline, say), the lead is still saved.
workspace_id string The team id, if you don't send X-Workspace-Id.
bash
curl https://api.supercool.com/api/v1/crm-app/leads \
  -H "Authorization: Bearer $SUPERCOOL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "email": "[email protected]",
    "first_name": "Jane",
    "phone": "+15555550123",
    "tags": ["webinar"],
    "consent": true,
    "sms_consent": true,
    "source": "pricing page",
    "attribution": { "utm_source": "google", "utm_campaign": "fall-promo" },
    "opportunity": { "pipeline": "Sales", "value": 50000 }
  }'

Returns 201:

json
{
  "ok": true,
  "created": true,
  "contact": { "id": "ct_8f2a6c1e9b3d5f7a0c4e", "email": "[email protected]", "phone": "+15555550123", "first_name": "Jane", "source": "lead_api" }
}

contact is the full contact (shortened here). Every lead fires the form_submitted and lead_received triggers.

Errors: 400 (email or phone is required, an invalid email or phone), 401, 403, 422 (wrong field types, like tags as a string), 429 (600 leads per minute per key).

Last updated