Reference
API reference
Every endpoint of the SuperCool API: parameters, request and response schemas, errors, and curl, Python and JavaScript samples.
Base URL: https://api.supercool.com/v1. Authenticate with Authorization: Bearer sc_key_… (Authentication). Requests and responses are JSON; errors share one shape. Download the spec as OpenAPI JSON or YAML.
Messages
Files
Account
Webhooks
CRM
- GET
/zapier/me - GET
/zapier/triggers - GET
/zapier/hooks - POST
/zapier/hooks - DELETE
/zapier/hooks - DELETE
/zapier/hooks/{hook_id} - GET
/zapier/perform-list/{event} - POST
/zapier/contacts - POST
/zapier/contacts/update - GET
/zapier/contacts/find - POST
/zapier/contacts/tags - POST
/zapier/contacts/tags/remove - POST
/zapier/opportunities - POST
/zapier/opportunities/{opportunity_id}/move - POST
/zapier/tasks - POST
/zapier/workflows/{workflow_id}/enroll - POST
/zapier/messages/sms - POST
/zapier/messages/email - GET
/zapier/pipelines - GET
/zapier/workflows - GET
/zapier/tags - GET
/zapier/team - GET
/zapier/fields - POST
/leads
#Messages
Talk to your agent and follow the work it starts.
#Send a message
POST /v1/messages
Sends your agent a message, optionally with files. The call returns within about 30
seconds: either the finished answer, or status: "processing" when the agent started
longer work. Follow a processing message with
GET /messages/{message_id} (use ?wait= to long-poll), its
events, or a webhook.
Returns 201 for a new message and 200 when an Idempotency-Key replays an
existing one. Running out of credits is not an error: the message comes back with
status: "blocked" and reason: "out_of_credits". Neither is the running-jobs cap:
work the agent can't start because 5 jobs are already running comes back as a
blocked job with reason: "too_many_running", and the message lists the running work.
Parameters
| Name | Type | Description |
|---|---|---|
Idempotency-Key | string header | Any string up to 128 characters, unique per logical request. Scoped to the API key and
kept for 7 days: sending it again with the same body returns the same message (200)
and never starts work twice; with a different body it's 409 idempotency_conflict.
|
Request body MessageCreate
| Field | Type | Description |
|---|---|---|
message | string | What you want, in plain language. Up to 20,000 characters; send longer text as a file. |
files | array of InlineFile | Small files inline (up to 5, 10 MB each, 25 MB in total). Each has a url or base64. A file that can't be fetched doesn't fail the message: it's listed in the message's notes. |
upload_ids | array of string | Finished uploads to attach (up to 10, 1 GB in total). See POST /uploads. |
webhook_endpoint_id | string | null | Send this message's webhook events to this registered endpoint (whe_…) instead of the account's endpoints. |
Responses
- 200 An
Idempotency-Keyyou already used with the same body. The same message, as it is now. Returns Message. - 201 The message was accepted. It is either final already or
processing. Returns Message. - 400 The request can't be processed as sent.
invalid_request(withproblems) when it doesn't match the schema. - 401 The API key is missing, unknown or revoked.
- 404 Nothing with that id for this API key or account.
- 409 The request conflicts with the current state.
- 413 A file or upload is over the size limit.
- 429 A rate limit was hit. Wait
Retry-Afterseconds.
Errors
400 invalid_request 400 empty_message 400 too_long 400 too_many 401 unauthorized 404 not_found 409 idempotency_conflict 409 upload_not_ready 413 too_large 429 rate_limited
Example request
curl -X POST "https://api.supercool.com/v1/messages" \
-H "Authorization: Bearer $SUPERCOOL_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: order-8812-ad" \
-d '{
"message": "A 15 second vertical ad for my candle shop, warm and cozy"
}'import os
import requests
resp = requests.post(
"https://api.supercool.com/v1/messages",
headers={"Authorization": f"Bearer {os.environ['SUPERCOOL_API_KEY']}", "Idempotency-Key": "order-8812-ad"},
json={
"message": "A 15 second vertical ad for my candle shop, warm and cozy",
},
timeout=60,
)
resp.raise_for_status()
print(resp.json())const res = await fetch("https://api.supercool.com/v1/messages", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.SUPERCOOL_API_KEY}`,
"Content-Type": "application/json",
"Idempotency-Key": "order-8812-ad",
},
body: JSON.stringify({
"message": "A 15 second vertical ad for my candle shop, warm and cozy"
}),
});
const data = await res.json();
if (!res.ok) throw new Error(`${data.error}: ${data.message} (${data.request_id})`);
console.log(data);#Get a message
GET /v1/messages/{message_id}
One message: its status, the agent's reply, the jobs it started and the files they
made. With wait, the call holds for up to that many seconds and returns as soon as
something changes (the status, the reply arriving, or a job's status) or the message
is final.
Messages are kept for at least 14 days after their last job's deadline. After that
this returns 404 not_found; read the work instead (GET /work/{work_id}).
Parameters
| Name | Type | Description |
|---|---|---|
message_id required | string path | The message's id (msg_…). |
wait | integer query | Long-poll for up to this many seconds (0 to 45). 0 returns immediately. Default 0. |
Responses
- 200 The message. Returns Message.
- 400 The request can't be processed as sent.
invalid_request(withproblems) when it doesn't match the schema. - 401 The API key is missing, unknown or revoked.
- 404 Nothing with that id for this API key or account.
- 429 A rate limit was hit. Wait
Retry-Afterseconds.
Errors
400 invalid_request 401 unauthorized 404 not_found 429 rate_limited
Example request
curl "https://api.supercool.com/v1/messages/msg_3f9a1c2e7b8d4e5f6a7b8c9d?wait=30" \
-H "Authorization: Bearer $SUPERCOOL_API_KEY"import os
import requests
resp = requests.get(
"https://api.supercool.com/v1/messages/msg_3f9a1c2e7b8d4e5f6a7b8c9d",
headers={"Authorization": f"Bearer {os.environ['SUPERCOOL_API_KEY']}"},
params={"wait": 30},
timeout=60,
)
resp.raise_for_status()
print(resp.json())const res = await fetch("https://api.supercool.com/v1/messages/msg_3f9a1c2e7b8d4e5f6a7b8c9d?wait=30", {
headers: {
Authorization: `Bearer ${process.env.SUPERCOOL_API_KEY}`,
},
});
const data = await res.json();
if (!res.ok) throw new Error(`${data.error}: ${data.message} (${data.request_id})`);
console.log(data);Example response 200
{
"id": "msg_3f9a1c2e7b8d4e5f6a7b8c9d",
"object": "message",
"status": "completed",
"reason": null,
"question": null,
"final": true,
"reply": "On it. I'm making a 15 second vertical ad with warm candlelight shots.",
"jobs": [
{
"job_id": "exe_9b2c4d6e8f0a1b3c5d7e9f1a2b4c6d8e",
"kind": "execution",
"role": "execution",
"work_id": "5d0c3e2a-8f1b-4c6d-9e7f-1a2b3c4d5e6f",
"title": "Candle shop vertical ad",
"status": "completed",
"reason": null,
"final": true,
"queued": null,
"resume_by": null,
"link": "https://supercool.com/chat/5d0c3e2a-8f1b-4c6d-9e7f-1a2b3c4d5e6f",
"started_at": "2026-09-30T14:02:11.402000Z",
"settled_at": "2026-09-30T14:06:40.221000Z",
"files": [
{
"file_id": "5d0c3e2a-8f1b-4c6d-9e7f-1a2b3c4d5e6f/candle_ad.mp4",
"name": "candle_ad.mp4",
"content_type": "video/mp4",
"kind": "video",
"size": 4812345,
"url": "https://api.supercool.sh/api/v1/files/s/eyJmIjoiNWQwYzNl...",
"revision": "\"5b1c0e6f9d2a4b7c8e1f3a5d7c9b0e2f\"",
"work_id": "5d0c3e2a-8f1b-4c6d-9e7f-1a2b3c4d5e6f",
"label": null
}
]
}
],
"files": [
{
"file_id": "5d0c3e2a-8f1b-4c6d-9e7f-1a2b3c4d5e6f/candle_ad.mp4",
"name": "candle_ad.mp4",
"content_type": "video/mp4",
"kind": "video",
"size": 4812345,
"url": "https://api.supercool.sh/api/v1/files/s/eyJmIjoiNWQwYzNl...",
"revision": "\"5b1c0e6f9d2a4b7c8e1f3a5d7c9b0e2f\"",
"work_id": "5d0c3e2a-8f1b-4c6d-9e7f-1a2b3c4d5e6f",
"label": null
}
],
"notes": [],
"credits_used": 42.5,
"created_at": "2026-09-30T14:02:03.118000Z",
"updated_at": "2026-09-30T14:02:12.950000Z"
}#List a message's events
GET /v1/messages/{message_id}/events
The message's update log, oldest first: progress steps, late replies, each job's
result with its files, and state changes. Only this message's entries: another
message's progress in the same chat isn't included. Pass back the cursor from the previous
page to get only what's new. With no cursor, the log starts where the message
started. done is true once the message has nothing more to report.
The call waits at least one second, and up to wait seconds, for new entries. Only
one wait per message runs at a time (409 poll_in_progress otherwise). Entries are
kept for 7 days; a cursor older than that returns cursor_expired: true with a fresh
cursor.
Parameters
| Name | Type | Description |
|---|---|---|
message_id required | string path | The message's id (msg_…). |
cursor | string query | The cursor from the previous page. Opaque. |
wait | integer query | Wait up to this many seconds for new entries (0 to 45; at least 1 second is always waited). Default 0. |
Responses
- 200 A page of events. Returns EventList.
- 400 The request can't be processed as sent.
invalid_request(withproblems) when it doesn't match the schema. - 401 The API key is missing, unknown or revoked.
- 404 Nothing with that id for this API key or account.
- 409 The request conflicts with the current state.
- 429 A rate limit was hit. Wait
Retry-Afterseconds.
Errors
400 invalid_request 400 bad_cursor 401 unauthorized 404 not_found 409 poll_in_progress 429 rate_limited
Example request
curl "https://api.supercool.com/v1/messages/msg_3f9a1c2e7b8d4e5f6a7b8c9d/events?wait=30" \
-H "Authorization: Bearer $SUPERCOOL_API_KEY"import os
import requests
resp = requests.get(
"https://api.supercool.com/v1/messages/msg_3f9a1c2e7b8d4e5f6a7b8c9d/events",
headers={"Authorization": f"Bearer {os.environ['SUPERCOOL_API_KEY']}"},
params={"wait": 30},
timeout=60,
)
resp.raise_for_status()
print(resp.json())const res = await fetch("https://api.supercool.com/v1/messages/msg_3f9a1c2e7b8d4e5f6a7b8c9d/events?wait=30", {
headers: {
Authorization: `Bearer ${process.env.SUPERCOOL_API_KEY}`,
},
});
const data = await res.json();
if (!res.ok) throw new Error(`${data.error}: ${data.message} (${data.request_id})`);
console.log(data);Example response 200
{
"object": "list",
"data": [
{
"seq": 118,
"type": "progress",
"work_id": "5d0c3e2a-8f1b-4c6d-9e7f-1a2b3c4d5e6f",
"message_id": "msg_3f9a1c2e7b8d4e5f6a7b8c9d",
"at": "2026-09-30T14:03:02.000000Z",
"step": "Rendering the video",
"status": "started",
"file_names": []
},
{
"seq": 121,
"type": "result",
"work_id": "5d0c3e2a-8f1b-4c6d-9e7f-1a2b3c4d5e6f",
"message_id": "msg_3f9a1c2e7b8d4e5f6a7b8c9d",
"at": "2026-09-30T14:06:40.000000Z",
"title": "Candle shop vertical ad",
"text": "Your 15 second ad is ready.",
"outcome": "completed",
"files": []
}
],
"has_more": false,
"done": true,
"cursor": "eyJjIjoiYXBpOnU..."
}#Recover a message's results
POST /v1/messages/{message_id}/recover
For work that outlived its watch (a job or message that expired): re-arms the watch
so the result is looked up again and lands in the message, its events and your
webhooks. It never resends the message or starts new work. One entry per piece of
work: settled (already has its outcome), watching (still being watched),
recovering (re-armed now) or exhausted (recovered too many times; read the work).
Parameters
| Name | Type | Description |
|---|---|---|
message_id required | string path | The message's id (msg_…). |
Responses
- 200 What happened to each piece of work. Returns Recovery.
- 401 The API key is missing, unknown or revoked.
- 404 Nothing with that id for this API key or account.
- 429 A rate limit was hit. Wait
Retry-Afterseconds.
Errors
401 unauthorized 404 not_found 429 rate_limited
Example request
curl -X POST "https://api.supercool.com/v1/messages/msg_3f9a1c2e7b8d4e5f6a7b8c9d/recover" \
-H "Authorization: Bearer $SUPERCOOL_API_KEY"import os
import requests
resp = requests.post(
"https://api.supercool.com/v1/messages/msg_3f9a1c2e7b8d4e5f6a7b8c9d/recover",
headers={"Authorization": f"Bearer {os.environ['SUPERCOOL_API_KEY']}"},
timeout=60,
)
resp.raise_for_status()
print(resp.json())const res = await fetch("https://api.supercool.com/v1/messages/msg_3f9a1c2e7b8d4e5f6a7b8c9d/recover", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.SUPERCOOL_API_KEY}`,
},
});
const data = await res.json();
if (!res.ok) throw new Error(`${data.error}: ${data.message} (${data.request_id})`);
console.log(data);Example response 200
{
"id": "msg_3f9a1c2e7b8d4e5f6a7b8c9d",
"object": "recovery",
"work": [
{
"work_id": "5d0c3e2a-8f1b-4c6d-9e7f-1a2b3c4d5e6f",
"state": "recovering"
}
]
}#Cancel a message's work
POST /v1/messages/{message_id}/cancel
Stops the work a message started. Work still queued (for credits, or for its chat) is
removed before it runs; work running now is stopped when it is this message's own run
(other work in the same chat is never stopped) and settles as cancelled a moment
later with the credits it used; work the agent starts for the message after this is
cancelled as it arrives. Credits already used stay on credits_used.
Returns the message, plus cancelled (job ids stopped) and not_cancellable (job ids
that couldn't be, such as a follow-up that joined another message's running work).
Cancelling finished work changes nothing.
Parameters
| Name | Type | Description |
|---|---|---|
message_id required | string path | The message's id (msg_…). |
Responses
- 200 The message after cancelling. Returns object.
- 401 The API key is missing, unknown or revoked.
- 404 Nothing with that id for this API key or account.
- 429 A rate limit was hit. Wait
Retry-Afterseconds.
Errors
401 unauthorized 404 not_found 429 rate_limited
Example request
curl -X POST "https://api.supercool.com/v1/messages/msg_3f9a1c2e7b8d4e5f6a7b8c9d/cancel" \
-H "Authorization: Bearer $SUPERCOOL_API_KEY"import os
import requests
resp = requests.post(
"https://api.supercool.com/v1/messages/msg_3f9a1c2e7b8d4e5f6a7b8c9d/cancel",
headers={"Authorization": f"Bearer {os.environ['SUPERCOOL_API_KEY']}"},
timeout=60,
)
resp.raise_for_status()
print(resp.json())const res = await fetch("https://api.supercool.com/v1/messages/msg_3f9a1c2e7b8d4e5f6a7b8c9d/cancel", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.SUPERCOOL_API_KEY}`,
},
});
const data = await res.json();
if (!res.ok) throw new Error(`${data.error}: ${data.message} (${data.request_id})`);
console.log(data);#Uploads
Attach large files (up to 500 MB each) to a message.
#Start an upload
POST /v1/uploads
Starts a large upload (up to 500 MB). Returns a presigned POST: send a
multipart/form-data POST to url with every entry of fields as form fields and
the file last, as a field named file. The link is valid for 15 minutes. Then call
POST /uploads/{upload_id}/complete and pass the upload_id in a
message's upload_ids. Uploads never attached to a message are deleted after 24 hours.
Request body UploadCreate
| Field | Type | Description |
|---|---|---|
filename required | string | |
size required | integer | The exact size in bytes (up to 500 MB). The upload is capped at this size. |
content_type | string | null | The MIME type. Guessed from the file name when omitted. The POST must send the same Content-Type field (it is in fields). |
Responses
- 201 The upload was created. POST the file to
urlnext. Returns UploadCreated. - 400 The request can't be processed as sent.
invalid_request(withproblems) when it doesn't match the schema. - 401 The API key is missing, unknown or revoked.
- 413 A file or upload is over the size limit.
- 429 A rate limit was hit. Wait
Retry-Afterseconds.
Errors
400 invalid_request 400 bad_size 401 unauthorized 413 too_large 429 rate_limited
Example request
curl -X POST "https://api.supercool.com/v1/uploads" \
-H "Authorization: Bearer $SUPERCOOL_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"filename": "product-shoot.mov",
"size": 157286400,
"content_type": "video/quicktime"
}'import os
import requests
resp = requests.post(
"https://api.supercool.com/v1/uploads",
headers={"Authorization": f"Bearer {os.environ['SUPERCOOL_API_KEY']}"},
json={
"filename": "product-shoot.mov",
"size": 157286400,
"content_type": "video/quicktime",
},
timeout=60,
)
resp.raise_for_status()
print(resp.json())const res = await fetch("https://api.supercool.com/v1/uploads", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.SUPERCOOL_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
"filename": "product-shoot.mov",
"size": 157286400,
"content_type": "video/quicktime"
}),
});
const data = await res.json();
if (!res.ok) throw new Error(`${data.error}: ${data.message} (${data.request_id})`);
console.log(data);Example response 201
{
"object": "upload",
"upload_id": "up_0a1b2c3d4e5f60718293a4b5c6d7e8f9",
"url": "https://supercool-uploads.s3.amazonaws.com/",
"fields": {
"key": "agent-uploads/incoming/.../product-shoot.mov",
"Content-Type": "video/quicktime",
"policy": "eyJleHBpcmF0aW9uIjoi...",
"x-amz-signature": "3f1c..."
},
"expires_at": "2026-09-30T14:17:03.118000Z",
"max_bytes": 157286400
}#Complete an upload
POST /v1/uploads/{upload_id}/complete
Finalizes an upload after the file was POSTed to the presigned url. The file is
checked (its real size, and executables are refused) and becomes ready to attach.
Calling it again on a ready upload returns the same result.
Parameters
| Name | Type | Description |
|---|---|---|
upload_id required | string path | The upload_id from POST /uploads (up_…). |
Responses
- 200 The upload is ready to attach. Returns Upload.
- 401 The API key is missing, unknown or revoked.
- 404 Nothing with that id for this API key or account.
- 409 The request conflicts with the current state.
- 413 A file or upload is over the size limit.
- 422 The uploaded file was refused (an executable).
- 429 A rate limit was hit. Wait
Retry-Afterseconds.
Errors
401 unauthorized 404 not_found 409 not_pending 409 not_uploaded 409 changed 413 too_large 422 executable_refused 429 rate_limited
Example request
curl -X POST "https://api.supercool.com/v1/uploads/up_0a1b2c3d4e5f60718293a4b5c6d7e8f9/complete" \
-H "Authorization: Bearer $SUPERCOOL_API_KEY"import os
import requests
resp = requests.post(
"https://api.supercool.com/v1/uploads/up_0a1b2c3d4e5f60718293a4b5c6d7e8f9/complete",
headers={"Authorization": f"Bearer {os.environ['SUPERCOOL_API_KEY']}"},
timeout=60,
)
resp.raise_for_status()
print(resp.json())const res = await fetch("https://api.supercool.com/v1/uploads/up_0a1b2c3d4e5f60718293a4b5c6d7e8f9/complete", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.SUPERCOOL_API_KEY}`,
},
});
const data = await res.json();
if (!res.ok) throw new Error(`${data.error}: ${data.message} (${data.request_id})`);
console.log(data);Example response 200
{
"object": "upload",
"upload_id": "up_0a1b2c3d4e5f60718293a4b5c6d7e8f9",
"status": "ready",
"filename": "product-shoot.mov",
"content_type": "video/quicktime",
"size": 157286400
}#Work
The chats your agent works in, the same ones you see in the app.
#List recent work
GET /v1/work
Your agent's recent work (chats), newest first: the same list you see in the app, whether the work was started over the API, in the app, by phone or anywhere else.
Parameters
| Name | Type | Description |
|---|---|---|
limit | integer query | How many to return (1 to 50). Default 20. |
Responses
- 200 Recent work. Returns WorkList.
- 400 The request can't be processed as sent.
invalid_request(withproblems) when it doesn't match the schema. - 401 The API key is missing, unknown or revoked.
- 429 A rate limit was hit. Wait
Retry-Afterseconds.
Errors
400 invalid_request 401 unauthorized 429 rate_limited
Example request
curl "https://api.supercool.com/v1/work?limit=10" \
-H "Authorization: Bearer $SUPERCOOL_API_KEY"import os
import requests
resp = requests.get(
"https://api.supercool.com/v1/work",
headers={"Authorization": f"Bearer {os.environ['SUPERCOOL_API_KEY']}"},
params={"limit": 10},
timeout=60,
)
resp.raise_for_status()
print(resp.json())const res = await fetch("https://api.supercool.com/v1/work?limit=10", {
headers: {
Authorization: `Bearer ${process.env.SUPERCOOL_API_KEY}`,
},
});
const data = await res.json();
if (!res.ok) throw new Error(`${data.error}: ${data.message} (${data.request_id})`);
console.log(data);Example response 200
{
"object": "list",
"data": [
{
"object": "work",
"work_id": "5d0c3e2a-8f1b-4c6d-9e7f-1a2b3c4d5e6f",
"title": "Candle shop vertical ad",
"preview": "Your 15 second ad is ready.",
"link": "https://supercool.com/chat/5d0c3e2a-8f1b-4c6d-9e7f-1a2b3c4d5e6f"
}
]
}#Get a piece of work
GET /v1/work/{work_id}
One piece of work: whether it's running, the latest result text (paged with
from_char / next_from_char) and its most recent files (up to 8), each with a
fresh download link.
Parameters
| Name | Type | Description |
|---|---|---|
work_id required | string path | The work_id from a job, an event or GET /work. |
from_char | integer query | Read the text from this character on (use the previous next_from_char). Default 0. |
Responses
- 200 The work. Returns Work.
- 400 The request can't be processed as sent.
invalid_request(withproblems) when it doesn't match the schema. - 401 The API key is missing, unknown or revoked.
- 404 Nothing with that id for this API key or account.
- 429 A rate limit was hit. Wait
Retry-Afterseconds.
Errors
400 invalid_request 401 unauthorized 404 not_found 429 rate_limited
Example request
curl "https://api.supercool.com/v1/work/5d0c3e2a-8f1b-4c6d-9e7f-1a2b3c4d5e6f" \
-H "Authorization: Bearer $SUPERCOOL_API_KEY"import os
import requests
resp = requests.get(
"https://api.supercool.com/v1/work/5d0c3e2a-8f1b-4c6d-9e7f-1a2b3c4d5e6f",
headers={"Authorization": f"Bearer {os.environ['SUPERCOOL_API_KEY']}"},
timeout=60,
)
resp.raise_for_status()
print(resp.json())const res = await fetch("https://api.supercool.com/v1/work/5d0c3e2a-8f1b-4c6d-9e7f-1a2b3c4d5e6f", {
headers: {
Authorization: `Bearer ${process.env.SUPERCOOL_API_KEY}`,
},
});
const data = await res.json();
if (!res.ok) throw new Error(`${data.error}: ${data.message} (${data.request_id})`);
console.log(data);Example response 200
{
"object": "work",
"work_id": "5d0c3e2a-8f1b-4c6d-9e7f-1a2b3c4d5e6f",
"title": "Candle shop vertical ad",
"status": "idle",
"text": "Your 15 second ad is ready. I used warm, slow push-ins on the candles...",
"next_from_char": null,
"link": "https://supercool.com/chat/5d0c3e2a-8f1b-4c6d-9e7f-1a2b3c4d5e6f",
"files": []
}#Files
Download what the agent made.
#Get a file
GET /v1/files/{file_id}
A file's record with a fresh signed download link. Use it when a url has expired,
or when a download answered 412 because the file changed since the link was made.
File ids contain slashes (<work_id>/<path>); put them in the path as they are.
Parameters
| Name | Type | Description |
|---|---|---|
file_id required | string path | The file_id from a message, job, event or work (<work_id>/<path>, slashes included). |
Responses
- 200 The file. Returns FileObject.
- 401 The API key is missing, unknown or revoked.
- 404 Nothing with that id for this API key or account.
- 429 A rate limit was hit. Wait
Retry-Afterseconds.
Errors
401 unauthorized 404 not_found 429 rate_limited
Example request
curl "https://api.supercool.com/v1/files/5d0c3e2a-8f1b-4c6d-9e7f-1a2b3c4d5e6f/candle_ad.mp4" \
-H "Authorization: Bearer $SUPERCOOL_API_KEY"import os
import requests
resp = requests.get(
"https://api.supercool.com/v1/files/5d0c3e2a-8f1b-4c6d-9e7f-1a2b3c4d5e6f/candle_ad.mp4",
headers={"Authorization": f"Bearer {os.environ['SUPERCOOL_API_KEY']}"},
timeout=60,
)
resp.raise_for_status()
print(resp.json())const res = await fetch("https://api.supercool.com/v1/files/5d0c3e2a-8f1b-4c6d-9e7f-1a2b3c4d5e6f/candle_ad.mp4", {
headers: {
Authorization: `Bearer ${process.env.SUPERCOOL_API_KEY}`,
},
});
const data = await res.json();
if (!res.ok) throw new Error(`${data.error}: ${data.message} (${data.request_id})`);
console.log(data);Example response 200
{
"object": "file",
"file_id": "5d0c3e2a-8f1b-4c6d-9e7f-1a2b3c4d5e6f/candle_ad.mp4",
"name": "candle_ad.mp4",
"content_type": "video/mp4",
"kind": "video",
"size": 4812345,
"url": "https://api.supercool.sh/api/v1/files/s/eyJmIjoiNWQwYzNl...",
"revision": "\"5b1c0e6f9d2a4b7c8e1f3a5d7c9b0e2f\"",
"work_id": "5d0c3e2a-8f1b-4c6d-9e7f-1a2b3c4d5e6f",
"label": null
}#Account
Who the key belongs to, the plan, credits and limits.
#Get the account
GET /v1/me
The account the API key belongs to, its plan, available credits, the agent's name, the key itself and your limits.
Responses
- 200 The account. Returns Account.
- 401 The API key is missing, unknown or revoked.
- 429 A rate limit was hit. Wait
Retry-Afterseconds.
Errors
401 unauthorized 429 rate_limited
Example request
curl "https://api.supercool.com/v1/me" \
-H "Authorization: Bearer $SUPERCOOL_API_KEY"import os
import requests
resp = requests.get(
"https://api.supercool.com/v1/me",
headers={"Authorization": f"Bearer {os.environ['SUPERCOOL_API_KEY']}"},
timeout=60,
)
resp.raise_for_status()
print(resp.json())const res = await fetch("https://api.supercool.com/v1/me", {
headers: {
Authorization: `Bearer ${process.env.SUPERCOOL_API_KEY}`,
},
});
const data = await res.json();
if (!res.ok) throw new Error(`${data.error}: ${data.message} (${data.request_id})`);
console.log(data);Example response 200
{
"object": "account",
"user_id": "64f1c2a9e4b0a1b2c3d4e5f6",
"email": "[email protected]",
"name": "Sam Rivera",
"plan": "pro",
"credits": 1840,
"agent": {
"name": "Nova"
},
"key": {
"id": "pat:6a1b2c3d4e5f",
"name": "prod server"
},
"limits": {
"running_jobs": 5,
"requests_per_minute": 600
}
}#Webhooks
Get pushed events instead of polling.
#List webhook endpoints
GET /v1/webhooks
The account's webhook endpoints (up to 10).
Responses
- 200 The endpoints. Returns WebhookEndpointList.
- 401 The API key is missing, unknown or revoked.
- 429 A rate limit was hit. Wait
Retry-Afterseconds.
Errors
401 unauthorized 429 rate_limited
Example request
curl "https://api.supercool.com/v1/webhooks" \
-H "Authorization: Bearer $SUPERCOOL_API_KEY"import os
import requests
resp = requests.get(
"https://api.supercool.com/v1/webhooks",
headers={"Authorization": f"Bearer {os.environ['SUPERCOOL_API_KEY']}"},
timeout=60,
)
resp.raise_for_status()
print(resp.json())const res = await fetch("https://api.supercool.com/v1/webhooks", {
headers: {
Authorization: `Bearer ${process.env.SUPERCOOL_API_KEY}`,
},
});
const data = await res.json();
if (!res.ok) throw new Error(`${data.error}: ${data.message} (${data.request_id})`);
console.log(data);Example response 200
{
"object": "list",
"data": [
{
"object": "webhook_endpoint",
"id": "whe_4c1d2e3f4a5b6c7d8e9f",
"url": "https://example.com/hooks/supercool",
"events": [
"*"
],
"description": "prod",
"created_at": "2026-09-30T13:40:00.000000Z",
"disabled_at": null,
"disabled_reason": null,
"failing_since": null
}
]
}#Create a webhook endpoint
POST /v1/webhooks
Registers a public https URL (port 443 or 8443) to receive events. The response
includes the endpoint's signing secret (whsec_…). It is shown only this once:
store it now. Omit events (or pass ["*"]) to receive every event.
Request body WebhookCreate
| Field | Type | Description |
|---|---|---|
url required | string | A public https URL (port 443 or 8443, up to 2,000 characters). |
events | array of string | null | Events to receive, or ["*"] for all (the default). |
description | string | null | Up to 120 characters. |
Responses
- 201 The endpoint, with its secret (shown once). Returns WebhookEndpointWithSecret.
- 400 The request can't be processed as sent.
invalid_request(withproblems) when it doesn't match the schema. - 401 The API key is missing, unknown or revoked.
- 409 The request conflicts with the current state.
- 429 A rate limit was hit. Wait
Retry-Afterseconds.
Errors
400 invalid_request 400 bad_url 400 bad_events 400 bad_request 401 unauthorized 409 too_many_endpoints 429 rate_limited
Example request
curl -X POST "https://api.supercool.com/v1/webhooks" \
-H "Authorization: Bearer $SUPERCOOL_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"url": "https://example.com/hooks/supercool",
"events": [
"message.completed",
"message.failed",
"message.blocked"
],
"description": "prod"
}'import os
import requests
resp = requests.post(
"https://api.supercool.com/v1/webhooks",
headers={"Authorization": f"Bearer {os.environ['SUPERCOOL_API_KEY']}"},
json={
"url": "https://example.com/hooks/supercool",
"events": [
"message.completed",
"message.failed",
"message.blocked",
],
"description": "prod",
},
timeout=60,
)
resp.raise_for_status()
print(resp.json())const res = await fetch("https://api.supercool.com/v1/webhooks", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.SUPERCOOL_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
"url": "https://example.com/hooks/supercool",
"events": [
"message.completed",
"message.failed",
"message.blocked"
],
"description": "prod"
}),
});
const data = await res.json();
if (!res.ok) throw new Error(`${data.error}: ${data.message} (${data.request_id})`);
console.log(data);Example response 201
{
"object": "webhook_endpoint",
"id": "whe_4c1d2e3f4a5b6c7d8e9f",
"url": "https://example.com/hooks/supercool",
"events": [
"message.completed",
"message.failed",
"message.blocked"
],
"description": "prod",
"created_at": "2026-09-30T13:40:00.000000Z",
"disabled_at": null,
"disabled_reason": null,
"failing_since": null,
"secret": "whsec_MfKQ9r8GKYqrTwjUPD8ILPZIo2LaLaSw"
}#Get a webhook endpoint
GET /v1/webhooks/{endpoint_id}
One endpoint and its 20 most recent deliveries.
Parameters
| Name | Type | Description |
|---|---|---|
endpoint_id required | string path | The webhook endpoint's id (whe_…). |
Responses
- 200 The endpoint. Returns WebhookEndpointDetail.
- 401 The API key is missing, unknown or revoked.
- 404 Nothing with that id for this API key or account.
- 429 A rate limit was hit. Wait
Retry-Afterseconds.
Errors
401 unauthorized 404 not_found 429 rate_limited
Example request
curl "https://api.supercool.com/v1/webhooks/whe_4c1d2e3f4a5b6c7d8e9f" \
-H "Authorization: Bearer $SUPERCOOL_API_KEY"import os
import requests
resp = requests.get(
"https://api.supercool.com/v1/webhooks/whe_4c1d2e3f4a5b6c7d8e9f",
headers={"Authorization": f"Bearer {os.environ['SUPERCOOL_API_KEY']}"},
timeout=60,
)
resp.raise_for_status()
print(resp.json())const res = await fetch("https://api.supercool.com/v1/webhooks/whe_4c1d2e3f4a5b6c7d8e9f", {
headers: {
Authorization: `Bearer ${process.env.SUPERCOOL_API_KEY}`,
},
});
const data = await res.json();
if (!res.ok) throw new Error(`${data.error}: ${data.message} (${data.request_id})`);
console.log(data);Example response 200
{
"object": "webhook_endpoint",
"id": "whe_4c1d2e3f4a5b6c7d8e9f",
"url": "https://example.com/hooks/supercool",
"events": [
"*"
],
"description": "prod",
"created_at": "2026-09-30T13:40:00.000000Z",
"disabled_at": null,
"disabled_reason": null,
"failing_since": null,
"recent_deliveries": [
{
"object": "webhook_delivery",
"id": "evt_7d8e9f0a1b2c3d4e5f6a7b8c",
"event": "message.completed",
"status": "delivered",
"attempts": 1,
"last_status_code": 200,
"last_error": null,
"last_response": "ok",
"last_ms": 184,
"created_at": "2026-09-30T14:06:41.000000Z",
"delivered_at": "2026-09-30T14:06:41.000000Z",
"next_attempt_at": "2026-09-30T14:06:41.000000Z",
"message_id": "msg_3f9a1c2e7b8d4e5f6a7b8c9d"
}
]
}#Update a webhook endpoint
PATCH /v1/webhooks/{endpoint_id}
Changes an endpoint's URL, events or description, or turns it off and on. Send only
the fields to change. "enabled": true turns a disabled endpoint back on (after you
fixed it) and clears its failure state.
Parameters
| Name | Type | Description |
|---|---|---|
endpoint_id required | string path | The webhook endpoint's id (whe_…). |
Request body WebhookUpdate
| Field | Type | Description |
|---|---|---|
url | string | null | |
events | array of string | null | |
description | string | null | |
enabled | boolean | null | true turns the endpoint back on and clears its failure state; false turns it off. |
Responses
- 200 The updated endpoint. Returns WebhookEndpoint.
- 400 The request can't be processed as sent.
invalid_request(withproblems) when it doesn't match the schema. - 401 The API key is missing, unknown or revoked.
- 404 Nothing with that id for this API key or account.
- 429 A rate limit was hit. Wait
Retry-Afterseconds.
Errors
400 invalid_request 400 bad_url 400 bad_events 400 bad_request 401 unauthorized 404 not_found 429 rate_limited
Example request
curl -X PATCH "https://api.supercool.com/v1/webhooks/whe_4c1d2e3f4a5b6c7d8e9f" \
-H "Authorization: Bearer $SUPERCOOL_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"enabled": true
}'import os
import requests
resp = requests.patch(
"https://api.supercool.com/v1/webhooks/whe_4c1d2e3f4a5b6c7d8e9f",
headers={"Authorization": f"Bearer {os.environ['SUPERCOOL_API_KEY']}"},
json={
"enabled": True,
},
timeout=60,
)
resp.raise_for_status()
print(resp.json())const res = await fetch("https://api.supercool.com/v1/webhooks/whe_4c1d2e3f4a5b6c7d8e9f", {
method: "PATCH",
headers: {
Authorization: `Bearer ${process.env.SUPERCOOL_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
"enabled": true
}),
});
const data = await res.json();
if (!res.ok) throw new Error(`${data.error}: ${data.message} (${data.request_id})`);
console.log(data);#Delete a webhook endpoint
DELETE /v1/webhooks/{endpoint_id}
Deletes the endpoint. Deliveries still waiting to be sent to it are cancelled.
Parameters
| Name | Type | Description |
|---|---|---|
endpoint_id required | string path | The webhook endpoint's id (whe_…). |
Responses
- 200 Deleted. Returns Deleted.
- 401 The API key is missing, unknown or revoked.
- 404 Nothing with that id for this API key or account.
- 429 A rate limit was hit. Wait
Retry-Afterseconds.
Errors
401 unauthorized 404 not_found 429 rate_limited
Example request
curl -X DELETE "https://api.supercool.com/v1/webhooks/whe_4c1d2e3f4a5b6c7d8e9f" \
-H "Authorization: Bearer $SUPERCOOL_API_KEY"import os
import requests
resp = requests.delete(
"https://api.supercool.com/v1/webhooks/whe_4c1d2e3f4a5b6c7d8e9f",
headers={"Authorization": f"Bearer {os.environ['SUPERCOOL_API_KEY']}"},
timeout=60,
)
resp.raise_for_status()
print(resp.json())const res = await fetch("https://api.supercool.com/v1/webhooks/whe_4c1d2e3f4a5b6c7d8e9f", {
method: "DELETE",
headers: {
Authorization: `Bearer ${process.env.SUPERCOOL_API_KEY}`,
},
});
const data = await res.json();
if (!res.ok) throw new Error(`${data.error}: ${data.message} (${data.request_id})`);
console.log(data);Example response 200
{
"object": "webhook_endpoint",
"id": "whe_4c1d2e3f4a5b6c7d8e9f",
"deleted": true
}#Rotate a signing secret
POST /v1/webhooks/{endpoint_id}/rotate-secret
Replaces the endpoint's signing secret and returns the new one (shown once). Every delivery from now on is signed with the new secret, including retries of earlier events.
Parameters
| Name | Type | Description |
|---|---|---|
endpoint_id required | string path | The webhook endpoint's id (whe_…). |
Responses
- 200 The new secret. Returns RotatedSecret.
- 401 The API key is missing, unknown or revoked.
- 404 Nothing with that id for this API key or account.
- 429 A rate limit was hit. Wait
Retry-Afterseconds.
Errors
401 unauthorized 404 not_found 429 rate_limited
Example request
curl -X POST "https://api.supercool.com/v1/webhooks/whe_4c1d2e3f4a5b6c7d8e9f/rotate-secret" \
-H "Authorization: Bearer $SUPERCOOL_API_KEY"import os
import requests
resp = requests.post(
"https://api.supercool.com/v1/webhooks/whe_4c1d2e3f4a5b6c7d8e9f/rotate-secret",
headers={"Authorization": f"Bearer {os.environ['SUPERCOOL_API_KEY']}"},
timeout=60,
)
resp.raise_for_status()
print(resp.json())const res = await fetch("https://api.supercool.com/v1/webhooks/whe_4c1d2e3f4a5b6c7d8e9f/rotate-secret", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.SUPERCOOL_API_KEY}`,
},
});
const data = await res.json();
if (!res.ok) throw new Error(`${data.error}: ${data.message} (${data.request_id})`);
console.log(data);Example response 200
{
"object": "webhook_endpoint",
"id": "whe_4c1d2e3f4a5b6c7d8e9f",
"secret": "whsec_Q2hhbmdlZC1zZWNyZXQtZXhhbXBsZTEy"
}#CRM
The CRM API behind the SuperCool Zapier app: contacts, tags, opportunities, tasks, workflows, texts and emails, REST hook triggers and the Lead API. Base URL https://api.supercool.com/api/v1/crm-app (not /v1), the same API keys, X-Workspace-Id for a team's CRM, and errors shaped {"detail": …}. Guide: CRM.
#Test a key
GET /api/v1/crm-app/zapier/me
Checks the API key and returns who it belongs to and which CRM it reaches: the key
owner's personal CRM, or the team named by X-Workspace-Id. Zapier calls it when
an account is connected and shows label as the connection's name.
Parameters
| Name | Type | Description |
|---|---|---|
X-Workspace-Id | string header | The team id, to use that team's CRM (the key's owner must be a member). Omit it for the key owner's personal CRM. Shown on the Zapier card in the CRM's Settings → Integrations. |
Responses
- 200 The key, its owner and the CRM. Returns CrmAccount.
- 401 The API key is missing, unknown, expired or revoked.
- 403 An isolated API key, or an
X-Workspace-Idteam the key's owner isn't a member of. - 429 Over the key's per-minute limit (300 on
/zapier/…, 600 on/leads). Wait and retry.
Example request
curl "https://api.supercool.com/api/v1/crm-app/zapier/me" \
-H "Authorization: Bearer $SUPERCOOL_API_KEY"import os
import requests
resp = requests.get(
"https://api.supercool.com/api/v1/crm-app/zapier/me",
headers={"Authorization": f"Bearer {os.environ['SUPERCOOL_API_KEY']}"},
timeout=60,
)
resp.raise_for_status()
print(resp.json())const res = await fetch("https://api.supercool.com/api/v1/crm-app/zapier/me", {
headers: {
Authorization: `Bearer ${process.env.SUPERCOOL_API_KEY}`,
},
});
const data = await res.json();
if (!res.ok) throw new Error(JSON.stringify(data.detail));
console.log(data);Example response 200
{
"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"
}#List triggers
GET /api/v1/crm-app/zapier/triggers
Every trigger key a REST hook can subscribe to, with the CRM event types it fires on. See the trigger catalogue.
Parameters
| Name | Type | Description |
|---|---|---|
X-Workspace-Id | string header | The team id, to use that team's CRM (the key's owner must be a member). Omit it for the key owner's personal CRM. Shown on the Zapier card in the CRM's Settings → Integrations. |
Responses
- 200 The triggers. Returns CrmTriggerList.
- 401 The API key is missing, unknown, expired or revoked.
- 403 An isolated API key, or an
X-Workspace-Idteam the key's owner isn't a member of. - 429 Over the key's per-minute limit (300 on
/zapier/…, 600 on/leads). Wait and retry.
Example request
curl "https://api.supercool.com/api/v1/crm-app/zapier/triggers" \
-H "Authorization: Bearer $SUPERCOOL_API_KEY"import os
import requests
resp = requests.get(
"https://api.supercool.com/api/v1/crm-app/zapier/triggers",
headers={"Authorization": f"Bearer {os.environ['SUPERCOOL_API_KEY']}"},
timeout=60,
)
resp.raise_for_status()
print(resp.json())const res = await fetch("https://api.supercool.com/api/v1/crm-app/zapier/triggers", {
headers: {
Authorization: `Bearer ${process.env.SUPERCOOL_API_KEY}`,
},
});
const data = await res.json();
if (!res.ok) throw new Error(JSON.stringify(data.detail));
console.log(data);Example response 200
{
"items": [
{
"key": "new_contact",
"label": "New Contact",
"noun": "Contact",
"types": [
"contact.created"
]
},
{
"key": "lead_received",
"label": "Lead Received",
"noun": "Lead",
"types": [
"facebook.lead_received",
"form.submitted"
]
}
]
}#List REST hook subscriptions
GET /api/v1/crm-app/zapier/hooks
The CRM's REST hook subscriptions, newest first, whichever key created them.
Parameters
| Name | Type | Description |
|---|---|---|
X-Workspace-Id | string header | The team id, to use that team's CRM (the key's owner must be a member). Omit it for the key owner's personal CRM. Shown on the Zapier card in the CRM's Settings → Integrations. |
Responses
- 200 The subscriptions. Returns CrmHookList.
- 401 The API key is missing, unknown, expired or revoked.
- 403 An isolated API key, or an
X-Workspace-Idteam the key's owner isn't a member of. - 429 Over the key's per-minute limit (300 on
/zapier/…, 600 on/leads). Wait and retry.
Example request
curl "https://api.supercool.com/api/v1/crm-app/zapier/hooks" \
-H "Authorization: Bearer $SUPERCOOL_API_KEY"import os
import requests
resp = requests.get(
"https://api.supercool.com/api/v1/crm-app/zapier/hooks",
headers={"Authorization": f"Bearer {os.environ['SUPERCOOL_API_KEY']}"},
timeout=60,
)
resp.raise_for_status()
print(resp.json())const res = await fetch("https://api.supercool.com/api/v1/crm-app/zapier/hooks", {
headers: {
Authorization: `Bearer ${process.env.SUPERCOOL_API_KEY}`,
},
});
const data = await res.json();
if (!res.ok) throw new Error(JSON.stringify(data.detail));
console.log(data);Example response 200
{
"items": [
{
"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": 12,
"failures": 0,
"last_delivery_at": "2026-10-23T14:02:12.010000Z",
"last_status": 200,
"last_error": null,
"created_at": "2026-10-23T13:40:00Z",
"updated_at": "2026-10-23T14:02:12.010000Z"
}
]
}#Subscribe a REST hook
POST /api/v1/crm-app/zapier/hooks
Subscribes target_url to one trigger. Each matching CRM event is then POSTed to it
(see Delivery). Subscribing the same URL to the same
trigger again returns the existing subscription. Up to 100 subscriptions per CRM.
Parameters
| Name | Type | Description |
|---|---|---|
X-Workspace-Id | string header | The team id, to use that team's CRM (the key's owner must be a member). Omit it for the key owner's personal CRM. Shown on the Zapier card in the CRM's Settings → Integrations. |
Request body CrmHookCreate
| Field | Type | Description |
|---|---|---|
event required | string | A trigger key (GET /zapier/triggers), like new_contact. |
target_url required | string | A public http or https URL. Private and local addresses are refused. Up to 2048 characters. |
Responses
- 201 The subscription. Returns CrmHook.
- 400 The input can't be used.
detailsays why. - 401 The API key is missing, unknown, expired or revoked.
- 403 An isolated API key, or an
X-Workspace-Idteam the key's owner isn't a member of. - 422 The body doesn't match the schema (a missing required field, a wrong type).
detaillists the problems. - 429 Over the key's per-minute limit (300 on
/zapier/…, 600 on/leads). Wait and retry.
Example request
curl -X POST "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"
}'import os
import requests
resp = requests.post(
"https://api.supercool.com/api/v1/crm-app/zapier/hooks",
headers={"Authorization": f"Bearer {os.environ['SUPERCOOL_API_KEY']}"},
json={
"event": "new_contact",
"target_url": "https://hooks.example.com/supercool/new-contact",
},
timeout=60,
)
resp.raise_for_status()
print(resp.json())const res = await fetch("https://api.supercool.com/api/v1/crm-app/zapier/hooks", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.SUPERCOOL_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
"event": "new_contact",
"target_url": "https://hooks.example.com/supercool/new-contact"
}),
});
const data = await res.json();
if (!res.ok) throw new Error(JSON.stringify(data.detail));
console.log(data);Example response 201
{
"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"
}#Unsubscribe by id or URL
DELETE /api/v1/crm-app/zapier/hooks
Removes a subscription by id, or every subscription to target_url (only the one
for event, if you send it). Events still waiting to be delivered to them are
dropped. 400 without id or target_url; 404 when nothing matches.
Parameters
| Name | Type | Description |
|---|---|---|
X-Workspace-Id | string header | The team id, to use that team's CRM (the key's owner must be a member). Omit it for the key owner's personal CRM. Shown on the Zapier card in the CRM's Settings → Integrations. |
Request body CrmHookDelete
| Field | Type | Description |
|---|---|---|
id | string | The subscription's id (zh_…). |
target_url | string | Remove every subscription to this URL. |
event | string | With target_url, only the subscription for this trigger. |
Responses
- 200 Removed. Returns CrmOk.
- 400 The input can't be used.
detailsays why. - 401 The API key is missing, unknown, expired or revoked.
- 403 An isolated API key, or an
X-Workspace-Idteam the key's owner isn't a member of. - 404 The contact, opportunity, workflow or subscription isn't in this CRM.
- 429 Over the key's per-minute limit (300 on
/zapier/…, 600 on/leads). Wait and retry.
Example request
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"
}'import os
import requests
resp = requests.delete(
"https://api.supercool.com/api/v1/crm-app/zapier/hooks",
headers={"Authorization": f"Bearer {os.environ['SUPERCOOL_API_KEY']}"},
json={
"target_url": "https://hooks.example.com/supercool/new-contact",
"event": "new_contact",
},
timeout=60,
)
resp.raise_for_status()
print(resp.json())const res = await fetch("https://api.supercool.com/api/v1/crm-app/zapier/hooks", {
method: "DELETE",
headers: {
Authorization: `Bearer ${process.env.SUPERCOOL_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
"target_url": "https://hooks.example.com/supercool/new-contact",
"event": "new_contact"
}),
});
const data = await res.json();
if (!res.ok) throw new Error(JSON.stringify(data.detail));
console.log(data);Example response 200
{
"ok": true
}#Unsubscribe a REST hook
DELETE /api/v1/crm-app/zapier/hooks/{hook_id}
Removes one subscription. Events still waiting to be delivered to it are dropped.
Parameters
| Name | Type | Description |
|---|---|---|
hook_id required | string path | The subscription's id (zh_…). |
X-Workspace-Id | string header | The team id, to use that team's CRM (the key's owner must be a member). Omit it for the key owner's personal CRM. Shown on the Zapier card in the CRM's Settings → Integrations. |
Responses
- 200 Removed. Returns CrmOk.
- 401 The API key is missing, unknown, expired or revoked.
- 403 An isolated API key, or an
X-Workspace-Idteam the key's owner isn't a member of. - 404 The contact, opportunity, workflow or subscription isn't in this CRM.
- 429 Over the key's per-minute limit (300 on
/zapier/…, 600 on/leads). Wait and retry.
Example request
curl -X DELETE "https://api.supercool.com/api/v1/crm-app/zapier/hooks/zh_4c1d2e3f4a5b6c7d8e9f" \
-H "Authorization: Bearer $SUPERCOOL_API_KEY"import os
import requests
resp = requests.delete(
"https://api.supercool.com/api/v1/crm-app/zapier/hooks/zh_4c1d2e3f4a5b6c7d8e9f",
headers={"Authorization": f"Bearer {os.environ['SUPERCOOL_API_KEY']}"},
timeout=60,
)
resp.raise_for_status()
print(resp.json())const res = await fetch("https://api.supercool.com/api/v1/crm-app/zapier/hooks/zh_4c1d2e3f4a5b6c7d8e9f", {
method: "DELETE",
headers: {
Authorization: `Bearer ${process.env.SUPERCOOL_API_KEY}`,
},
});
const data = await res.json();
if (!res.ok) throw new Error(JSON.stringify(data.detail));
console.log(data);Example response 200
{
"ok": true
}#Get sample events
GET /api/v1/crm-app/zapier/perform-list/{event}
Up to 3 of the CRM's most recent events for a trigger, newest first, in the payload a
subscription receives. With none yet, one made-up sample ("id": "ev_sample").
Parameters
| Name | Type | Description |
|---|---|---|
event required | string path | A trigger key, such as new_contact. See GET /zapier/triggers. |
X-Workspace-Id | string header | The team id, to use that team's CRM (the key's owner must be a member). Omit it for the key owner's personal CRM. Shown on the Zapier card in the CRM's Settings → Integrations. |
Responses
- 200 The events. Returns CrmEventList.
- 400 The input can't be used.
detailsays why. - 401 The API key is missing, unknown, expired or revoked.
- 403 An isolated API key, or an
X-Workspace-Idteam the key's owner isn't a member of. - 429 Over the key's per-minute limit (300 on
/zapier/…, 600 on/leads). Wait and retry.
Example request
curl "https://api.supercool.com/api/v1/crm-app/zapier/perform-list/new_contact" \
-H "Authorization: Bearer $SUPERCOOL_API_KEY"import os
import requests
resp = requests.get(
"https://api.supercool.com/api/v1/crm-app/zapier/perform-list/new_contact",
headers={"Authorization": f"Bearer {os.environ['SUPERCOOL_API_KEY']}"},
timeout=60,
)
resp.raise_for_status()
print(resp.json())const res = await fetch("https://api.supercool.com/api/v1/crm-app/zapier/perform-list/new_contact", {
headers: {
Authorization: `Bearer ${process.env.SUPERCOOL_API_KEY}`,
},
});
const data = await res.json();
if (!res.ok) throw new Error(JSON.stringify(data.detail));
console.log(data);Example response 200
{
"items": [
{
"id": "ev_3f9a1c2e7b8d4e5f6a7b",
"trigger": "tag_added",
"type": "contact.tag_added",
"label": "Contact tag added",
"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": "zapier",
"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": {
"tag_id": "tag_1a2b3c4d5e6f7a8b9c0d",
"tag_name": "lead"
},
"ref": {},
"actor": {
"kind": "api_key",
"id": "pat:6a1b2c3d4e5f"
}
}
]
}#Create or update a contact
POST /api/v1/crm-app/zapier/contacts
Finds the contact by email, then phone, and updates it, or creates it (source
zapier). Only non-empty fields are written. consent: true records explicit email
consent and sms_consent: true explicit SMS consent; explicit consent is never
downgraded. Returns the contact at the top level, with ok and created.
Parameters
| Name | Type | Description |
|---|---|---|
X-Workspace-Id | string header | The team id, to use that team's CRM (the key's owner must be a member). Omit it for the key owner's personal CRM. Shown on the Zapier card in the CRM's Settings → Integrations. |
Request body CrmContactWrite
| Field | Type | Description |
|---|---|---|
contact_id | string | Update only. The contact's id. |
contact | string | Update only. An id, email address or phone number. |
email | string | |
phone | string | Stored in E.164. US numbers without a country code get +1. |
first_name | string | |
last_name | string | |
company | string | |
address | string | |
city | string | |
state | string | |
postal_code | string | |
country | string | |
website | string | |
owner_user_id | string | A team member's user id (GET /zapier/team). |
tags | array of string or string CrmTags | Tag names, as a list or one comma-separated string (up to 50, 80 characters each). |
fields | object | Custom field values by key (GET /zapier/fields). Create or update ignores unknown keys; update refuses them. |
consent | boolean | Create or update only. true records explicit email consent. Otherwise email consent is imported_unknown, unless already explicit. |
sms_consent | boolean | Create or update only. true records explicit SMS consent, if the contact has a phone number. |
Responses
- 200 The contact, created or updated. Returns CrmContactResult.
- 400 The input can't be used.
detailsays why. - 401 The API key is missing, unknown, expired or revoked.
- 403 An isolated API key, or an
X-Workspace-Idteam the key's owner isn't a member of. - 422 The body doesn't match the schema (a missing required field, a wrong type).
detaillists the problems. - 429 Over the key's per-minute limit (300 on
/zapier/…, 600 on/leads). Wait and retry. - 503 The feature isn't available on this account yet.
Example request
curl -X POST "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
}'import os
import requests
resp = requests.post(
"https://api.supercool.com/api/v1/crm-app/zapier/contacts",
headers={"Authorization": f"Bearer {os.environ['SUPERCOOL_API_KEY']}"},
json={
"email": "[email protected]",
"first_name": "Jane",
"last_name": "Doe",
"phone": "(555) 555-0123",
"tags": "lead, webinar",
"consent": True,
},
timeout=60,
)
resp.raise_for_status()
print(resp.json())const res = await fetch("https://api.supercool.com/api/v1/crm-app/zapier/contacts", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.SUPERCOOL_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
"email": "[email protected]",
"first_name": "Jane",
"last_name": "Doe",
"phone": "(555) 555-0123",
"tags": "lead, webinar",
"consent": true
}),
});
const data = await res.json();
if (!res.ok) throw new Error(JSON.stringify(data.detail));
console.log(data);Example response 200
{
"ok": true,
"created": true,
"id": "ct_8f2a6c1e9b3d5f7a0c4e",
"email": "[email protected]",
"phone": "+15555550123",
"first_name": "Jane",
"last_name": "Doe",
"company": null,
"display_name": "Jane Doe",
"tags": [
"tag_1a2b3c4d5e6f7a8b9c0d",
"tag_0d9c8b7a6f5e4d3c2b1a"
],
"fields": {},
"source": "zapier",
"owner_user_id": null,
"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
}
},
"last_activity_at": "2026-10-23T14:02:11.402000Z",
"created_at": "2026-10-23T14:02:11.402000Z",
"updated_at": "2026-10-23T14:02:11.402000Z"
}#Update a contact
POST /api/v1/crm-app/zapier/contacts/update
Changes an existing contact, found by contact_id, contact, email or phone.
Only the fields you send change; tags are added. Custom field keys must exist.
consent and sms_consent are ignored. 400 Nothing to update without changes.
Parameters
| Name | Type | Description |
|---|---|---|
X-Workspace-Id | string header | The team id, to use that team's CRM (the key's owner must be a member). Omit it for the key owner's personal CRM. Shown on the Zapier card in the CRM's Settings → Integrations. |
Request body CrmContactWrite
| Field | Type | Description |
|---|---|---|
contact_id | string | Update only. The contact's id. |
contact | string | Update only. An id, email address or phone number. |
email | string | |
phone | string | Stored in E.164. US numbers without a country code get +1. |
first_name | string | |
last_name | string | |
company | string | |
address | string | |
city | string | |
state | string | |
postal_code | string | |
country | string | |
website | string | |
owner_user_id | string | A team member's user id (GET /zapier/team). |
tags | array of string or string CrmTags | Tag names, as a list or one comma-separated string (up to 50, 80 characters each). |
fields | object | Custom field values by key (GET /zapier/fields). Create or update ignores unknown keys; update refuses them. |
consent | boolean | Create or update only. true records explicit email consent. Otherwise email consent is imported_unknown, unless already explicit. |
sms_consent | boolean | Create or update only. true records explicit SMS consent, if the contact has a phone number. |
Responses
- 200 The updated contact, at the top level, with
ok. Returns CrmContactResult. - 400 The input can't be used.
detailsays why. - 401 The API key is missing, unknown, expired or revoked.
- 403 An isolated API key, or an
X-Workspace-Idteam 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 schema (a missing required field, a wrong type).
detaillists the problems. - 429 Over the key's per-minute limit (300 on
/zapier/…, 600 on/leads). Wait and retry.
Example request
curl -X POST "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"
]
}'import os
import requests
resp = requests.post(
"https://api.supercool.com/api/v1/crm-app/zapier/contacts/update",
headers={"Authorization": f"Bearer {os.environ['SUPERCOOL_API_KEY']}"},
json={
"contact": "[email protected]",
"company": "Example Co",
"tags": [
"customer",
],
},
timeout=60,
)
resp.raise_for_status()
print(resp.json())const res = await fetch("https://api.supercool.com/api/v1/crm-app/zapier/contacts/update", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.SUPERCOOL_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
"contact": "[email protected]",
"company": "Example Co",
"tags": [
"customer"
]
}),
});
const data = await res.json();
if (!res.ok) throw new Error(JSON.stringify(data.detail));
console.log(data);#Find a contact
GET /api/v1/crm-app/zapier/contacts/find
Looks a contact up by id, then email, then phone. No match is an empty list, never a 404.
Parameters
| Name | Type | Description |
|---|---|---|
id | string query | The contact's id (ct_…). |
contact | string query | An id, email address or phone number. |
email | string query | The contact's email address. |
phone | string query | The contact's phone number. |
X-Workspace-Id | string header | The team id, to use that team's CRM (the key's owner must be a member). Omit it for the key owner's personal CRM. Shown on the Zapier card in the CRM's Settings → Integrations. |
Responses
- 200 The contact, or an empty list. Returns CrmContactList.
- 401 The API key is missing, unknown, expired or revoked.
- 403 An isolated API key, or an
X-Workspace-Idteam the key's owner isn't a member of. - 429 Over the key's per-minute limit (300 on
/zapier/…, 600 on/leads). Wait and retry.
Example request
curl "https://api.supercool.com/api/v1/crm-app/zapier/contacts/find?email=jane%40example.com" \
-H "Authorization: Bearer $SUPERCOOL_API_KEY"import os
import requests
resp = requests.get(
"https://api.supercool.com/api/v1/crm-app/zapier/contacts/find",
headers={"Authorization": f"Bearer {os.environ['SUPERCOOL_API_KEY']}"},
params={"email": "[email protected]"},
timeout=60,
)
resp.raise_for_status()
print(resp.json())const res = await fetch("https://api.supercool.com/api/v1/crm-app/zapier/contacts/find?email=jane%40example.com", {
headers: {
Authorization: `Bearer ${process.env.SUPERCOOL_API_KEY}`,
},
});
const data = await res.json();
if (!res.ok) throw new Error(JSON.stringify(data.detail));
console.log(data);Example response 200
{
"items": [
{
"id": "ct_8f2a6c1e9b3d5f7a0c4e",
"email": "[email protected]",
"phone": "+15555550123",
"first_name": "Jane",
"last_name": "Doe",
"display_name": "Jane Doe",
"tags": [],
"fields": {},
"source": "zapier",
"status": "active"
}
]
}#Add tags to a contact
POST /api/v1/crm-app/zapier/contacts/tags
Adds tags by name; missing tags are created. Tags the contact already has change nothing.
Parameters
| Name | Type | Description |
|---|---|---|
X-Workspace-Id | string header | The team id, to use that team's CRM (the key's owner must be a member). Omit it for the key owner's personal CRM. Shown on the Zapier card in the CRM's Settings → Integrations. |
Request body CrmTagsChange
| Field | Type | Description |
|---|---|---|
contact_id | string | The contact's id (ct_…). |
contact | string | An id, email address or phone number. |
email | string | |
phone | string | |
tags required | array of string or string CrmTags | Tag names, as a list or one comma-separated string (up to 50, 80 characters each). |
Responses
- 200 The contact, at the top level, with
ok. Returns CrmContactResult. - 400 The input can't be used.
detailsays why. - 401 The API key is missing, unknown, expired or revoked.
- 403 An isolated API key, or an
X-Workspace-Idteam the key's owner isn't a member of. - 404 The contact, opportunity, workflow or subscription isn't in this CRM.
- 429 Over the key's per-minute limit (300 on
/zapier/…, 600 on/leads). Wait and retry.
Example request
curl -X POST "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"
]
}'import os
import requests
resp = requests.post(
"https://api.supercool.com/api/v1/crm-app/zapier/contacts/tags",
headers={"Authorization": f"Bearer {os.environ['SUPERCOOL_API_KEY']}"},
json={
"contact_id": "ct_8f2a6c1e9b3d5f7a0c4e",
"tags": [
"vip",
"webinar",
],
},
timeout=60,
)
resp.raise_for_status()
print(resp.json())const res = await fetch("https://api.supercool.com/api/v1/crm-app/zapier/contacts/tags", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.SUPERCOOL_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
"contact_id": "ct_8f2a6c1e9b3d5f7a0c4e",
"tags": [
"vip",
"webinar"
]
}),
});
const data = await res.json();
if (!res.ok) throw new Error(JSON.stringify(data.detail));
console.log(data);#Remove tags from a contact
POST /api/v1/crm-app/zapier/contacts/tags/remove
Removes tags given by name (ignoring case and extra spaces) or id (tag_…). Tags the contact doesn't have are skipped.
Parameters
| Name | Type | Description |
|---|---|---|
X-Workspace-Id | string header | The team id, to use that team's CRM (the key's owner must be a member). Omit it for the key owner's personal CRM. Shown on the Zapier card in the CRM's Settings → Integrations. |
Request body CrmTagsChange
| Field | Type | Description |
|---|---|---|
contact_id | string | The contact's id (ct_…). |
contact | string | An id, email address or phone number. |
email | string | |
phone | string | |
tags required | array of string or string CrmTags | Tag names, as a list or one comma-separated string (up to 50, 80 characters each). |
Responses
- 200 The contact, at the top level, with
ok. Returns CrmContactResult. - 400 The input can't be used.
detailsays why. - 401 The API key is missing, unknown, expired or revoked.
- 403 An isolated API key, or an
X-Workspace-Idteam the key's owner isn't a member of. - 404 The contact, opportunity, workflow or subscription isn't in this CRM.
- 429 Over the key's per-minute limit (300 on
/zapier/…, 600 on/leads). Wait and retry.
Example request
curl -X POST "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"
}'import os
import requests
resp = requests.post(
"https://api.supercool.com/api/v1/crm-app/zapier/contacts/tags/remove",
headers={"Authorization": f"Bearer {os.environ['SUPERCOOL_API_KEY']}"},
json={
"email": "[email protected]",
"tags": "webinar",
},
timeout=60,
)
resp.raise_for_status()
print(resp.json())const res = await fetch("https://api.supercool.com/api/v1/crm-app/zapier/contacts/tags/remove", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.SUPERCOOL_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
"email": "[email protected]",
"tags": "webinar"
}),
});
const data = await res.json();
if (!res.ok) throw new Error(JSON.stringify(data.detail));
console.log(data);#Create an opportunity
POST /api/v1/crm-app/zapier/opportunities
Opens an opportunity for a contact, in a pipeline and stage given by id or name (the
default pipeline and its first stage when omitted). One open opportunity per
contact per pipeline unless allow_duplicate.
Parameters
| Name | Type | Description |
|---|---|---|
X-Workspace-Id | string header | The team id, to use that team's CRM (the key's owner must be a member). Omit it for the key owner's personal CRM. Shown on the Zapier card in the CRM's Settings → Integrations. |
Request body CrmOpportunityCreate
| Field | Type | Description |
|---|---|---|
contact_id | string | The contact's id (ct_…). |
contact | string | An id, email address or phone number. |
email | string | |
phone | string | |
pipeline_id | string | The pipeline's id (pl_…). With neither this nor pipeline, the default pipeline. |
pipeline | string | The pipeline's id or name. |
stage_id | string | The stage's id (st_…) or name. With neither this nor stage, the first stage. |
stage | string | The stage's id or name (any case). |
name | string | Defaults to the contact's name. |
value | number or string | A number is in minor units (150000 is 1,500.00); a string is in major units ("1,500.00"). |
currency | string | A 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 | array of string or string CrmTags | Tag names, as a list or one comma-separated string (up to 50, 80 characters each). |
fields | object | Opportunity custom field values by key. |
expected_close_at | string | YYYY-MM-DD. |
allow_duplicate | boolean | Allow a second open opportunity for the contact in this pipeline. |
Responses
- 201 The opportunity. Returns CrmOpportunity.
- 400 The input can't be used.
detailsays why. - 401 The API key is missing, unknown, expired or revoked.
- 403 An isolated API key, or an
X-Workspace-Idteam 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 schema (a missing required field, a wrong type).
detaillists the problems. - 429 Over the key's per-minute limit (300 on
/zapier/…, 600 on/leads). Wait and retry. - 503 The feature isn't available on this account yet.
Example request
curl -X POST "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"
}'import os
import requests
resp = requests.post(
"https://api.supercool.com/api/v1/crm-app/zapier/opportunities",
headers={"Authorization": f"Bearer {os.environ['SUPERCOOL_API_KEY']}"},
json={
"email": "[email protected]",
"pipeline": "Sales",
"stage": "Qualified",
"value": "1500",
},
timeout=60,
)
resp.raise_for_status()
print(resp.json())const res = await fetch("https://api.supercool.com/api/v1/crm-app/zapier/opportunities", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.SUPERCOOL_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
"email": "[email protected]",
"pipeline": "Sales",
"stage": "Qualified",
"value": "1500"
}),
});
const data = await res.json();
if (!res.ok) throw new Error(JSON.stringify(data.detail));
console.log(data);Example response 201
{
"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,
"followers": [],
"tags": [],
"fields": {},
"lost_reason": null,
"expected_close_at": null,
"created_at": "2026-10-23T14:05:00.118000Z",
"updated_at": "2026-10-23T14:05:00.118000Z"
}#Move an opportunity
POST /api/v1/crm-app/zapier/opportunities/{opportunity_id}/move
Moves an opportunity to another stage (by id or name), to another pipeline (its first stage unless a stage is given), and/or sets its status. Send at least one of stage, pipeline or status.
Parameters
| Name | Type | Description |
|---|---|---|
opportunity_id required | string path | The opportunity's id (opp_…). |
X-Workspace-Id | string header | The team id, to use that team's CRM (the key's owner must be a member). Omit it for the key owner's personal CRM. Shown on the Zapier card in the CRM's Settings → Integrations. |
Request body CrmOpportunityMove
| Field | Type | Description |
|---|---|---|
stage_id | string | The new stage's id or name. |
stage | string | The new stage's id or name (any case). |
pipeline_id | string | Move to this pipeline (its first stage unless a stage is given). |
pipeline | string | The pipeline's id or name. |
status | string | One of open, won, lost, abandoned. |
lost_reason | string | With status lost. |
Responses
- 200 The opportunity. Returns CrmOpportunity.
- 400 The input can't be used.
detailsays why. - 401 The API key is missing, unknown, expired or revoked.
- 403 An isolated API key, or an
X-Workspace-Idteam the key's owner isn't a member of. - 404 The contact, opportunity, workflow or subscription isn't in this CRM.
- 429 Over the key's per-minute limit (300 on
/zapier/…, 600 on/leads). Wait and retry. - 503 The feature isn't available on this account yet.
Example request
curl -X POST "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"
}'import os
import requests
resp = requests.post(
"https://api.supercool.com/api/v1/crm-app/zapier/opportunities/opp_7d8e9f0a1b2c3d4e5f6a/move",
headers={"Authorization": f"Bearer {os.environ['SUPERCOOL_API_KEY']}"},
json={
"stage": "Proposal sent",
},
timeout=60,
)
resp.raise_for_status()
print(resp.json())const res = await fetch("https://api.supercool.com/api/v1/crm-app/zapier/opportunities/opp_7d8e9f0a1b2c3d4e5f6a/move", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.SUPERCOOL_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
"stage": "Proposal sent"
}),
});
const data = await res.json();
if (!res.ok) throw new Error(JSON.stringify(data.detail));
console.log(data);#Create a task
POST /api/v1/crm-app/zapier/tasks
Adds a task, optionally on a contact (found like the other actions; 404 if given and not found).
Parameters
| Name | Type | Description |
|---|---|---|
X-Workspace-Id | string header | The team id, to use that team's CRM (the key's owner must be a member). Omit it for the key owner's personal CRM. Shown on the Zapier card in the CRM's Settings → Integrations. |
Request body CrmTaskCreate
| Field | Type | Description |
|---|---|---|
contact_id | string | The contact's id (ct_…). |
contact | string | An id, email address or phone number. |
email | string | |
phone | string | |
title required | string | Longer titles are cut to 200 characters. |
description | string | |
opportunity_id | string | |
assigned_to | string | A team member's user id. |
due_at | string | An exact time. Wins over due_date. |
due_date | string | YYYY-MM-DD. Without due_time, due all day. |
due_time | string | HH:MM. |
timezone | string | The IANA zone of due_date and due_time. Defaults to the assignee's zone, else the CRM's, else UTC. |
Responses
- 201 The task. Returns CrmTask.
- 400 The input can't be used.
detailsays why. - 401 The API key is missing, unknown, expired or revoked.
- 403 An isolated API key, or an
X-Workspace-Idteam 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 schema (a missing required field, a wrong type).
detaillists the problems. - 429 Over the key's per-minute limit (300 on
/zapier/…, 600 on/leads). Wait and retry. - 503 The feature isn't available on this account yet.
Example request
curl -X POST "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"
}'import os
import requests
resp = requests.post(
"https://api.supercool.com/api/v1/crm-app/zapier/tasks",
headers={"Authorization": f"Bearer {os.environ['SUPERCOOL_API_KEY']}"},
json={
"title": "Call Jane",
"email": "[email protected]",
"due_date": "2026-10-27",
"due_time": "10:00",
"timezone": "America/Chicago",
},
timeout=60,
)
resp.raise_for_status()
print(resp.json())const res = await fetch("https://api.supercool.com/api/v1/crm-app/zapier/tasks", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.SUPERCOOL_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
"title": "Call Jane",
"email": "[email protected]",
"due_date": "2026-10-27",
"due_time": "10:00",
"timezone": "America/Chicago"
}),
});
const data = await res.json();
if (!res.ok) throw new Error(JSON.stringify(data.detail));
console.log(data);Example response 201
{
"id": "tsk_1e2f3a4b5c6d7e8f9a0b",
"title": "Call Jane",
"description_html": null,
"contact_id": "ct_8f2a6c1e9b3d5f7a0c4e",
"opportunity_id": null,
"assigned_to": null,
"due_at": "2026-10-27T15:00:00Z",
"due_date": "2026-10-27",
"due_time": "10:00",
"all_day": false,
"timezone": "America/Chicago",
"status": "open",
"origin": {
"kind": "manual",
"reason": "Created by Zapier"
},
"created_at": "2026-10-23T14:08:00Z",
"updated_at": "2026-10-23T14:08:00Z"
}#Add a contact to a workflow
POST /api/v1/crm-app/zapier/workflows/{workflow_id}/enroll
Starts a published workflow for a contact. Not being enrolled is not an error: ok
is false and reason says why (already_active, no_reentry, not_published).
Parameters
| Name | Type | Description |
|---|---|---|
workflow_id required | string path | The workflow's id (wf_…). See GET /zapier/workflows. |
X-Workspace-Id | string header | The team id, to use that team's CRM (the key's owner must be a member). Omit it for the key owner's personal CRM. Shown on the Zapier card in the CRM's Settings → Integrations. |
Request body CrmContactLookup
| Field | Type | Description |
|---|---|---|
contact_id | string | The contact's id (ct_…). |
contact | string | An id, email address or phone number. |
email | string | |
phone | string |
Responses
- 200 The outcome. Returns CrmEnrollment.
- 401 The API key is missing, unknown, expired or revoked.
- 403 An isolated API key, or an
X-Workspace-Idteam the key's owner isn't a member of. - 404 The contact, opportunity, workflow or subscription isn't in this CRM.
- 429 Over the key's per-minute limit (300 on
/zapier/…, 600 on/leads). Wait and retry. - 503 The feature isn't available on this account yet.
Example request
curl -X POST "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]"
}'import os
import requests
resp = requests.post(
"https://api.supercool.com/api/v1/crm-app/zapier/workflows/wf_0a1b2c3d4e5f6a7b8c9d/enroll",
headers={"Authorization": f"Bearer {os.environ['SUPERCOOL_API_KEY']}"},
json={
"email": "[email protected]",
},
timeout=60,
)
resp.raise_for_status()
print(resp.json())const res = await fetch("https://api.supercool.com/api/v1/crm-app/zapier/workflows/wf_0a1b2c3d4e5f6a7b8c9d/enroll", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.SUPERCOOL_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
"email": "[email protected]"
}),
});
const data = await res.json();
if (!res.ok) throw new Error(JSON.stringify(data.detail));
console.log(data);Example response 200
{
"ok": true,
"reason": "enrolled",
"enrollment_id": "wfe_6f7a8b9c0d1e2f3a4b5c",
"workflow_id": "wf_0a1b2c3d4e5f6a7b8c9d",
"workflow_name": "New lead follow-up",
"contact_id": "ct_8f2a6c1e9b3d5f7a0c4e"
}#Send a text
POST /api/v1/crm-app/zapier/messages/sms
Texts a contact from the CRM's phone number. A marketing send: the contact needs
explicit SMS consent, and DND, STOP opt-outs, quiet hours, daily limits and credits
apply. A refused send is 422 with a reason; nothing is sent or queued. See
Consent rules.
Parameters
| Name | Type | Description |
|---|---|---|
X-Workspace-Id | string header | The team id, to use that team's CRM (the key's owner must be a member). Omit it for the key owner's personal CRM. Shown on the Zapier card in the CRM's Settings → Integrations. |
Request body CrmTextSend
| Field | Type | Description |
|---|---|---|
contact_id | string | The contact's id (ct_…). |
contact | string | An id, email address or phone number. |
email | string | |
phone | string | |
text | string | Required (or message). Cut at 1,600 characters; over 10 segments with the opt-out footer is refused (too_long). |
message | string | Same as text. |
media | array of string | One image file already stored in SuperCool (its storage key), up to 1 MB, for MMS. Anything else is refused (media). |
Responses
- 201 Sent (see
status). Returns CrmSent. - 400 The input can't be used.
detailsays why. - 401 The API key is missing, unknown, expired or revoked.
- 403 An isolated API key, or an
X-Workspace-Idteam the key's owner isn't a member of. - 404 The contact, opportunity, workflow or subscription isn't in this CRM.
- 422 The CRM's rules refused the send (
detailis{reason, message}; see refusal reasons), or the body doesn't match the schema (detailis a list). - 429 Over the key's per-minute limit (300 on
/zapier/…, 600 on/leads). Wait and retry. - 503 The feature isn't available on this account yet.
Example request
curl -X POST "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."
}'import os
import requests
resp = requests.post(
"https://api.supercool.com/api/v1/crm-app/zapier/messages/sms",
headers={"Authorization": f"Bearer {os.environ['SUPERCOOL_API_KEY']}"},
json={
"contact": "+15555550123",
"text": "Hi Jane, your order shipped today.",
},
timeout=60,
)
resp.raise_for_status()
print(resp.json())const res = await fetch("https://api.supercool.com/api/v1/crm-app/zapier/messages/sms", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.SUPERCOOL_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
"contact": "+15555550123",
"text": "Hi Jane, your order shipped today."
}),
});
const data = await res.json();
if (!res.ok) throw new Error(JSON.stringify(data.detail));
console.log(data);Example response 201
{
"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"
}#Send an email
POST /api/v1/crm-app/zapier/messages/email
Emails a contact from the CRM's default sender. A marketing send: the contact needs
explicit email consent, and DND, unsubscribes, bounces, daily limits and credits
apply. A refused send is 422 with a reason; nothing is sent or queued. See
Consent rules.
Parameters
| Name | Type | Description |
|---|---|---|
X-Workspace-Id | string header | The team id, to use that team's CRM (the key's owner must be a member). Omit it for the key owner's personal CRM. Shown on the Zapier card in the CRM's Settings → Integrations. |
Request body CrmEmailSend
| Field | Type | Description |
|---|---|---|
contact_id | string | The contact's id (ct_…). |
contact | string | An id, email address or phone number. |
email | string | |
phone | string | |
subject | string | Required. Cut at 300 characters. |
html | string | The HTML body. html, body or text is required. |
body | string | Same as html. |
text | string | A plain-text body. Without html, it also becomes the HTML body. |
Responses
- 201 Sent (see
status). Returns CrmSent. - 400 The input can't be used.
detailsays why. - 401 The API key is missing, unknown, expired or revoked.
- 403 An isolated API key, or an
X-Workspace-Idteam the key's owner isn't a member of. - 404 The contact, opportunity, workflow or subscription isn't in this CRM.
- 422 The CRM's rules refused the send (
detailis{reason, message}; see refusal reasons), or the body doesn't match the schema (detailis a list). - 429 Over the key's per-minute limit (300 on
/zapier/…, 600 on/leads). Wait and retry. - 503 The feature isn't available on this account yet.
Example request
curl -X POST "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."
}'import os
import requests
resp = requests.post(
"https://api.supercool.com/api/v1/crm-app/zapier/messages/email",
headers={"Authorization": f"Bearer {os.environ['SUPERCOOL_API_KEY']}"},
json={
"email": "[email protected]",
"subject": "Your order shipped",
"text": "Hi Jane,\nyour order shipped today.",
},
timeout=60,
)
resp.raise_for_status()
print(resp.json())const res = await fetch("https://api.supercool.com/api/v1/crm-app/zapier/messages/email", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.SUPERCOOL_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
"email": "[email protected]",
"subject": "Your order shipped",
"text": "Hi Jane,\nyour order shipped today."
}),
});
const data = await res.json();
if (!res.ok) throw new Error(JSON.stringify(data.detail));
console.log(data);#List pipelines
GET /api/v1/crm-app/zapier/pipelines
The CRM's pipelines and their stages, in order.
Parameters
| Name | Type | Description |
|---|---|---|
X-Workspace-Id | string header | The team id, to use that team's CRM (the key's owner must be a member). Omit it for the key owner's personal CRM. Shown on the Zapier card in the CRM's Settings → Integrations. |
Responses
- 200 The pipelines. Returns CrmPipelineList.
- 401 The API key is missing, unknown, expired or revoked.
- 403 An isolated API key, or an
X-Workspace-Idteam the key's owner isn't a member of. - 429 Over the key's per-minute limit (300 on
/zapier/…, 600 on/leads). Wait and retry. - 503 The feature isn't available on this account yet.
Example request
curl "https://api.supercool.com/api/v1/crm-app/zapier/pipelines" \
-H "Authorization: Bearer $SUPERCOOL_API_KEY"import os
import requests
resp = requests.get(
"https://api.supercool.com/api/v1/crm-app/zapier/pipelines",
headers={"Authorization": f"Bearer {os.environ['SUPERCOOL_API_KEY']}"},
timeout=60,
)
resp.raise_for_status()
print(resp.json())const res = await fetch("https://api.supercool.com/api/v1/crm-app/zapier/pipelines", {
headers: {
Authorization: `Bearer ${process.env.SUPERCOOL_API_KEY}`,
},
});
const data = await res.json();
if (!res.ok) throw new Error(JSON.stringify(data.detail));
console.log(data);Example response 200
{
"items": [
{
"id": "pl_2b3c4d5e6f7a8b9c0d1e",
"name": "Sales",
"stages": [
{
"id": "st_4d5e6f7a8b9c0d1e2f3a",
"name": "New lead",
"position": 0
},
{
"id": "st_5e6f7a8b9c0d1e2f3a4b",
"name": "Qualified",
"position": 1
}
]
}
]
}#List workflows
GET /api/v1/crm-app/zapier/workflows
The CRM's published workflows.
Parameters
| Name | Type | Description |
|---|---|---|
X-Workspace-Id | string header | The team id, to use that team's CRM (the key's owner must be a member). Omit it for the key owner's personal CRM. Shown on the Zapier card in the CRM's Settings → Integrations. |
Responses
- 200 The workflows. Returns CrmWorkflowList.
- 401 The API key is missing, unknown, expired or revoked.
- 403 An isolated API key, or an
X-Workspace-Idteam the key's owner isn't a member of. - 429 Over the key's per-minute limit (300 on
/zapier/…, 600 on/leads). Wait and retry. - 503 The feature isn't available on this account yet.
Example request
curl "https://api.supercool.com/api/v1/crm-app/zapier/workflows" \
-H "Authorization: Bearer $SUPERCOOL_API_KEY"import os
import requests
resp = requests.get(
"https://api.supercool.com/api/v1/crm-app/zapier/workflows",
headers={"Authorization": f"Bearer {os.environ['SUPERCOOL_API_KEY']}"},
timeout=60,
)
resp.raise_for_status()
print(resp.json())const res = await fetch("https://api.supercool.com/api/v1/crm-app/zapier/workflows", {
headers: {
Authorization: `Bearer ${process.env.SUPERCOOL_API_KEY}`,
},
});
const data = await res.json();
if (!res.ok) throw new Error(JSON.stringify(data.detail));
console.log(data);Example response 200
{
"items": [
{
"id": "wf_0a1b2c3d4e5f6a7b8c9d",
"name": "New lead follow-up",
"status": "published"
}
]
}#List tags
GET /api/v1/crm-app/zapier/tags
The CRM's tags.
Parameters
| Name | Type | Description |
|---|---|---|
X-Workspace-Id | string header | The team id, to use that team's CRM (the key's owner must be a member). Omit it for the key owner's personal CRM. Shown on the Zapier card in the CRM's Settings → Integrations. |
Responses
- 200 The tags. Returns CrmTagList.
- 401 The API key is missing, unknown, expired or revoked.
- 403 An isolated API key, or an
X-Workspace-Idteam the key's owner isn't a member of. - 429 Over the key's per-minute limit (300 on
/zapier/…, 600 on/leads). Wait and retry.
Example request
curl "https://api.supercool.com/api/v1/crm-app/zapier/tags" \
-H "Authorization: Bearer $SUPERCOOL_API_KEY"import os
import requests
resp = requests.get(
"https://api.supercool.com/api/v1/crm-app/zapier/tags",
headers={"Authorization": f"Bearer {os.environ['SUPERCOOL_API_KEY']}"},
timeout=60,
)
resp.raise_for_status()
print(resp.json())const res = await fetch("https://api.supercool.com/api/v1/crm-app/zapier/tags", {
headers: {
Authorization: `Bearer ${process.env.SUPERCOOL_API_KEY}`,
},
});
const data = await res.json();
if (!res.ok) throw new Error(JSON.stringify(data.detail));
console.log(data);Example response 200
{
"items": [
{
"id": "tag_1a2b3c4d5e6f7a8b9c0d",
"name": "lead"
}
]
}#List team members
GET /api/v1/crm-app/zapier/team
The team's members (in a personal CRM, just you). Their id is what owner_user_id and assigned_to take.
Parameters
| Name | Type | Description |
|---|---|---|
X-Workspace-Id | string header | The team id, to use that team's CRM (the key's owner must be a member). Omit it for the key owner's personal CRM. Shown on the Zapier card in the CRM's Settings → Integrations. |
Responses
- 200 The members. Returns CrmTeamList.
- 401 The API key is missing, unknown, expired or revoked.
- 403 An isolated API key, or an
X-Workspace-Idteam the key's owner isn't a member of. - 429 Over the key's per-minute limit (300 on
/zapier/…, 600 on/leads). Wait and retry.
Example request
curl "https://api.supercool.com/api/v1/crm-app/zapier/team" \
-H "Authorization: Bearer $SUPERCOOL_API_KEY"import os
import requests
resp = requests.get(
"https://api.supercool.com/api/v1/crm-app/zapier/team",
headers={"Authorization": f"Bearer {os.environ['SUPERCOOL_API_KEY']}"},
timeout=60,
)
resp.raise_for_status()
print(resp.json())const res = await fetch("https://api.supercool.com/api/v1/crm-app/zapier/team", {
headers: {
Authorization: `Bearer ${process.env.SUPERCOOL_API_KEY}`,
},
});
const data = await res.json();
if (!res.ok) throw new Error(JSON.stringify(data.detail));
console.log(data);Example response 200
{
"items": [
{
"id": "64f1c2a9e4b0a1b2c3d4e5f6",
"name": "Sam Rivera",
"email": "[email protected]",
"role": "admin"
}
]
}#List contact custom fields
GET /api/v1/crm-app/zapier/fields
The contact custom fields. Their key is what fields takes.
Parameters
| Name | Type | Description |
|---|---|---|
X-Workspace-Id | string header | The team id, to use that team's CRM (the key's owner must be a member). Omit it for the key owner's personal CRM. Shown on the Zapier card in the CRM's Settings → Integrations. |
Responses
- 200 The fields. Returns CrmFieldList.
- 401 The API key is missing, unknown, expired or revoked.
- 403 An isolated API key, or an
X-Workspace-Idteam the key's owner isn't a member of. - 429 Over the key's per-minute limit (300 on
/zapier/…, 600 on/leads). Wait and retry.
Example request
curl "https://api.supercool.com/api/v1/crm-app/zapier/fields" \
-H "Authorization: Bearer $SUPERCOOL_API_KEY"import os
import requests
resp = requests.get(
"https://api.supercool.com/api/v1/crm-app/zapier/fields",
headers={"Authorization": f"Bearer {os.environ['SUPERCOOL_API_KEY']}"},
timeout=60,
)
resp.raise_for_status()
print(resp.json())const res = await fetch("https://api.supercool.com/api/v1/crm-app/zapier/fields", {
headers: {
Authorization: `Bearer ${process.env.SUPERCOOL_API_KEY}`,
},
});
const data = await res.json();
if (!res.ok) throw new Error(JSON.stringify(data.detail));
console.log(data);Example response 200
{
"items": [
{
"key": "budget",
"label": "Budget",
"type": "number"
}
]
}#Post a lead
POST /api/v1/crm-app/leads
The Lead API. Creates the contact, or updates the one with the same email or phone,
with source lead_api, plus optional attribution and an opportunity. Fires the
form_submitted and lead_received triggers. Up to 600 leads per minute per key.
Parameters
| Name | Type | Description |
|---|---|---|
X-Workspace-Id | string header | The team id, to use that team's CRM (the key's owner must be a member). Omit it for the key owner's personal CRM. Shown on the Zapier card in the CRM's Settings → Integrations. |
Request body CrmLeadCreate
| Field | Type | Description |
|---|---|---|
email | string | |
phone | string | |
first_name | string | |
last_name | string | |
company | string | |
address | string | |
city | string | |
state | string | |
postal_code | string | |
country | string | |
website | string | |
tags | array of string | Tag names to add (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. Otherwise imported_unknown, unless already explicit. |
sms_consent | boolean | true records explicit SMS consent, if the lead has a phone number. |
source | string | Your label for where the lead came from. Kept as lead_source. |
attribution | object | utm_source, utm_medium, utm_campaign, utm_term, utm_content, gclid, fbclid, referrer, landing_url. The first becomes the contact's first touch; each updates the last touch. |
opportunity | object | Add the lead to a pipeline: pipeline_id or pipeline (name), stage_id, value (minor units if a number, major if a string), name. If it can't be created, the lead is still saved. |
workspace_id | string | The team id, if you don't send X-Workspace-Id. |
Responses
- 201 The lead's contact. Returns CrmLeadCreated.
- 400 The input can't be used.
detailsays why. - 401 The API key is missing, unknown, expired or revoked.
- 403 An isolated API key, or an
X-Workspace-Idteam the key's owner isn't a member of. - 422 The body doesn't match the schema (a missing required field, a wrong type).
detaillists the problems. - 429 Over the key's per-minute limit (300 on
/zapier/…, 600 on/leads). Wait and retry.
Example request
curl -X POST "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
}
}'import os
import requests
resp = requests.post(
"https://api.supercool.com/api/v1/crm-app/leads",
headers={"Authorization": f"Bearer {os.environ['SUPERCOOL_API_KEY']}"},
json={
"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,
},
},
timeout=60,
)
resp.raise_for_status()
print(resp.json())const res = await fetch("https://api.supercool.com/api/v1/crm-app/leads", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.SUPERCOOL_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
"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
}
}),
});
const data = await res.json();
if (!res.ok) throw new Error(JSON.stringify(data.detail));
console.log(data);Example response 201
{
"ok": true,
"created": true,
"contact": {
"id": "ct_8f2a6c1e9b3d5f7a0c4e",
"email": "[email protected]",
"phone": "+15555550123",
"first_name": "Jane",
"last_name": null,
"display_name": "Jane",
"tags": [
"tag_9f8e7d6c5b4a39281706"
],
"fields": {},
"source": "lead_api",
"status": "active"
}
}#Webhook events
What SuperCool POSTs to your endpoints. See Webhooks for signatures and retries.
#A job changed
Sent when one job (one piece of work) changes: job.completed, job.failed,
job.blocked, job.resumed or job.expired. data is the job, with its
message_id and files. Signed with the Standard Webhooks headers.
Headers
| Header | Type | Description |
|---|---|---|
webhook-id required | string | The delivery's id (evt_…). The same across retries of one event; use it to drop duplicates. |
webhook-timestamp required | string | Unix seconds when this attempt was signed. |
webhook-signature required | string | v1, + base64 HMAC-SHA256 of {webhook-id}.{webhook-timestamp}.{body}, keyed with the base64-decoded part of your whsec_ secret. |
Payload
| Field | Type | Description |
|---|---|---|
type | string | One of job.completed, job.failed, job.blocked, job.resumed, job.expired. |
timestamp | string | |
data | object | |
object | "job" | |
message_id | string | |
job_id | string | exe_… for work that ran or is queued; ref_… for work refused before it was queued. |
kind | string | One of execution, refusal. |
role | string | follow_up when the message joined work that was already running; its status follows that work. One of execution, follow_up. |
work_id | string | null | The chat the work runs in. |
title | string | null | |
status | string JobStatus | One of running, completed, failed, cancelled, blocked, expired. |
reason | string | null | For blocked, failed, cancelled and expired: out_of_credits, hosting_fees, too_many_running, execution_failed, stopped, queue_expired, watch_expired, result_unknown. |
final | boolean | false while running, and for queued work waiting for credits (it can still run). |
queued | boolean | null | true for work that is queued and waiting for credits; otherwise null. |
resume_by | string | null | For queued work waiting for credits, the deadline to add them (24 hours after it was queued). |
link | string | null | The work in the SuperCool app. |
started_at | string | null | |
settled_at | string | null | |
files | array of File |
Example payload
{
"type": "job.completed",
"timestamp": "2026-09-30T14:06:40.310000Z",
"data": {
"object": "job",
"message_id": "msg_3f9a1c2e7b8d4e5f6a7b8c9d",
"job_id": "exe_9b2c4d6e8f0a1b3c5d7e9f1a2b4c6d8e",
"kind": "execution",
"role": "execution",
"work_id": "5d0c3e2a-8f1b-4c6d-9e7f-1a2b3c4d5e6f",
"title": "Candle shop vertical ad",
"status": "completed",
"reason": null,
"final": true,
"queued": null,
"resume_by": null,
"link": "https://supercool.com/chat/5d0c3e2a-8f1b-4c6d-9e7f-1a2b3c4d5e6f",
"started_at": "2026-09-30T14:02:11.402000Z",
"settled_at": "2026-09-30T14:06:40.221000Z",
"files": []
}
}#A message reached a status
Sent once each time a message reaches a status other than processing:
message.completed, message.partial, message.failed, message.needs_input,
message.blocked or message.expired. data is the full message, exactly as
GET /messages/{message_id} returns it.
Headers
| Header | Type | Description |
|---|---|---|
webhook-id required | string | The delivery's id (evt_…). The same across retries of one event; use it to drop duplicates. |
webhook-timestamp required | string | Unix seconds when this attempt was signed. |
webhook-signature required | string | v1, + base64 HMAC-SHA256 of {webhook-id}.{webhook-timestamp}.{body}, keyed with the base64-decoded part of your whsec_ secret. |
Payload
| Field | Type | Description |
|---|---|---|
type | string | One of message.completed, message.partial, message.failed, message.needs_input, message.blocked, message.expired. |
timestamp | string | |
data | Message | A message you sent and everything that came of it. |
#Objects
#MessageCreate
A message to your agent. Send message, files, or both.
| Field | Type | Description |
|---|---|---|
message | string | What you want, in plain language. Up to 20,000 characters; send longer text as a file. |
files | array of InlineFile | Small files inline (up to 5, 10 MB each, 25 MB in total). Each has a url or base64. A file that can't be fetched doesn't fail the message: it's listed in the message's notes. |
upload_ids | array of string | Finished uploads to attach (up to 10, 1 GB in total). See POST /uploads. |
webhook_endpoint_id | string | null | Send this message's webhook events to this registered endpoint (whe_…) instead of the account's endpoints. |
#InlineFile
| Field | Type | Description |
|---|---|---|
name | string | null | A file name for the agent to see. |
url | string | null | A public https URL to fetch the file from. |
base64 | string | null | The file's bytes, base64-encoded. |
content_type | string | null | The MIME type, for base64 files. |
#Message
A message you sent and everything that came of it.
| Field | Type | Description |
|---|---|---|
id required | string | The message id (msg_…), always generated by SuperCool. |
object required | "message" | |
status required | string MessageStatus | One of processing, needs_input, blocked, completed, partial, failed, expired. |
reason | string | null | Why, for blocked, failed, partial and expired: out_of_credits, hosting_fees, too_many_running, queue_expired, watch_expired, result_unknown, execution_failed, stopped, agent_busy, turn_failed and others. See Getting results. |
question | string | null | For needs_input, the agent's question. Answer by sending a new message. |
final required | boolean | true when nothing about this message will change any more. Stop polling. |
reply | string | null | What the agent said, once it has answered. |
jobs required | array of Job | The pieces of work this message started, each with its own status. |
files required | array of File | Every file the message produced (handed over in the reply, or made by its jobs). |
notes required | array of string | Problems with attachments (a file that couldn't be fetched, files over the limit). |
credits_used required | number | Credits this message's jobs used so far, in API units (hundredths of the figure the app shows; see Units). |
created_at required | string | |
updated_at required | string | When the agent's turn ended (or created_at while it's still going). |
resume | "add_credits" | Present when queued work is waiting for credits: add credits before resume_by and it runs by itself. Don't resend the message. |
resume_by | string | The earliest deadline among queued jobs waiting for credits (24 hours after they were queued). |
retry_after_seconds | integer | With reason: "agent_busy": retry with the same Idempotency-Key after this many seconds. |
running | array of object | Present when the agent tried to start work while 5 jobs were already running on the account (a job with reason: "too_many_running"): the work running now, and the message that started each. |
job_id | string | exe_… |
message_id | string | |
work_id | string |
#MessageStatus
The message's status, aggregated over its jobs.
string One of processing, needs_input, blocked, completed, partial, failed, expired.
#Job
One piece of work a message started (or was refused).
| Field | Type | Description |
|---|---|---|
job_id | string | exe_… for work that ran or is queued; ref_… for work refused before it was queued. |
kind | string | One of execution, refusal. |
role | string | follow_up when the message joined work that was already running; its status follows that work. One of execution, follow_up. |
work_id | string | null | The chat the work runs in. |
title | string | null | |
status | string JobStatus | One of running, completed, failed, cancelled, blocked, expired. |
reason | string | null | For blocked, failed, cancelled and expired: out_of_credits, hosting_fees, too_many_running, execution_failed, stopped, queue_expired, watch_expired, result_unknown. |
final | boolean | false while running, and for queued work waiting for credits (it can still run). |
queued | boolean | null | true for work that is queued and waiting for credits; otherwise null. |
resume_by | string | null | For queued work waiting for credits, the deadline to add them (24 hours after it was queued). |
link | string | null | The work in the SuperCool app. |
started_at | string | null | |
settled_at | string | null | |
files | array of File |
#JobStatus
string One of running, completed, failed, cancelled, blocked, expired.
#File
A file the agent made or handed over.
| Field | Type | Description |
|---|---|---|
file_id | string | <work_id>/<path>. Use it with GET /files/{file_id} for a fresh link. |
name | string | |
content_type | string | null | |
kind | string | One of image, video, audio, doc, site. |
size | integer | null | Bytes. null for a site. |
url | string | null | A signed download link. It expires, and it is pinned to revision; a download answers 412 if the file changed since. |
revision | string | null | The file's version the link is pinned to. null for a site (its link always opens the live site). |
work_id | string | null | |
label | string | null |
#FileObject
A file the agent made or handed over.
| Field | Type | Description |
|---|---|---|
object | "file" | |
file_id | string | <work_id>/<path>. Use it with GET /files/{file_id} for a fresh link. |
name | string | |
content_type | string | null | |
kind | string | One of image, video, audio, doc, site. |
size | integer | null | Bytes. null for a site. |
url | string | null | A signed download link. It expires, and it is pinned to revision; a download answers 412 if the file changed since. |
revision | string | null | The file's version the link is pinned to. null for a site (its link always opens the live site). |
work_id | string | null | |
label | string | null |
#EventList
| Field | Type | Description |
|---|---|---|
object | "list" | |
data | array of Event | |
has_more | boolean | More entries are ready; call again with cursor right away. |
done | boolean | The message has nothing more to report. |
cursor | string | Pass this back as cursor for the next page. |
cursor_expired | boolean | Your cursor pointed at entries older than 7 days. cursor restarts from now. |
#Event
One entry in a message's update log. Fields beyond the common ones depend on type.
| Field | Type | Description |
|---|---|---|
seq | integer | |
type | string | result (a job finished, with text and files), progress (a step started or a file landed), agent_reply (the agent's reply), state (resumed, stopped, expired, unknown), turn_failed, turn_busy. One of result, progress, agent_reply, state, turn_failed, turn_busy. |
work_id | string | null | |
message_id | string | null | The message the entry belongs to, when known. |
at | string | |
job_id | string | The job (exe_…) the entry belongs to, when known. |
title | string | null | result |
text | string | null | result, agent_reply, state, turn_failed, turn_busy |
outcome | string | result One of completed, failed, stalled_credits. |
files | array of File | result, agent_reply |
state | string | state |
step | string | null | progress |
status | string | null | progress |
file_names | array of string | progress: names of files that just landed (download them from the result). |
delivered_sync | boolean | null | agent_reply: true when this reply was already in the POST /messages response. |
reason | string | null | turn_failed, turn_busy |
#Recovery
| Field | Type | Description |
|---|---|---|
id | string | |
object | "recovery" | |
work | array of object | |
work_id | string | |
state | string | One of settled, watching, recovering, exhausted. |
outcome | string | For settled, how it ended. |
#UploadCreate
| Field | Type | Description |
|---|---|---|
filename required | string | |
size required | integer | The exact size in bytes (up to 500 MB). The upload is capped at this size. |
content_type | string | null | The MIME type. Guessed from the file name when omitted. The POST must send the same Content-Type field (it is in fields). |
#UploadCreated
| Field | Type | Description |
|---|---|---|
object | "upload" | |
upload_id | string | |
url | string | Where to POST the file (multipart/form-data). |
fields | object | Form fields to send with the file, exactly as given. |
expires_at | string | The presigned POST works until then (15 minutes). |
max_bytes | integer |
#Upload
| Field | Type | Description |
|---|---|---|
object | "upload" | |
upload_id | string | |
status | string | One of ready. |
filename | string | |
content_type | string | |
size | integer |
#WorkList
| Field | Type | Description |
|---|---|---|
object | "list" | |
data | array of WorkSummary |
#WorkSummary
| Field | Type | Description |
|---|---|---|
object | "work" | |
work_id | string | |
title | string | null | |
preview | string | null | |
link | string |
#Work
| Field | Type | Description |
|---|---|---|
object | "work" | |
work_id | string | |
title | string | null | |
status | string | running, stalled_credits (stopped until credits are added), or idle. One of running, stalled_credits, idle. |
text | string | The latest result text, from from_char. |
next_from_char | integer | null | Pass as from_char to read on; null when there's no more. |
link | string | |
files | array of File | The most recent files (up to 8). |
#Account
| Field | Type | Description |
|---|---|---|
object | "account" | |
user_id | string | |
email | string | null | |
name | string | null | |
plan | string | null | |
credits | number | null | Credits available now, in API units (hundredths of the figure the app shows; see Units). |
agent | object | |
name | string | |
key | object | |
id | string | |
name | string | |
limits | object | |
running_jobs | integer | Running jobs allowed per account (5). |
requests_per_minute | integer | Requests allowed per API key per minute (600). |
#WebhookEventType
string One of job.completed, job.failed, job.blocked, job.resumed, job.expired, message.completed, message.partial, message.failed, message.needs_input, message.blocked, message.expired.
#WebhookCreate
| Field | Type | Description |
|---|---|---|
url required | string | A public https URL (port 443 or 8443, up to 2,000 characters). |
events | array of string | null | Events to receive, or ["*"] for all (the default). |
description | string | null | Up to 120 characters. |
#WebhookUpdate
| Field | Type | Description |
|---|---|---|
url | string | null | |
events | array of string | null | |
description | string | null | |
enabled | boolean | null | true turns the endpoint back on and clears its failure state; false turns it off. |
#WebhookEndpoint
| Field | Type | Description |
|---|---|---|
object | "webhook_endpoint" | |
id | string | whe_… |
url | string | |
events | array of string | |
description | string | null | |
created_at | string | |
disabled_at | string | null | Set when the endpoint is off (by you, or after 3 days of failed deliveries). |
disabled_reason | string | null | |
failing_since | string | null | When deliveries started failing. Cleared by the next success. |
#WebhookEndpointWithSecret
| Field | Type | Description |
|---|---|---|
object | "webhook_endpoint" | |
id | string | whe_… |
url | string | |
events | array of string | |
description | string | null | |
created_at | string | |
disabled_at | string | null | Set when the endpoint is off (by you, or after 3 days of failed deliveries). |
disabled_reason | string | null | |
failing_since | string | null | When deliveries started failing. Cleared by the next success. |
secret | string | The signing secret (whsec_…). Shown only in this response. |
#WebhookEndpointDetail
| Field | Type | Description |
|---|---|---|
object | "webhook_endpoint" | |
id | string | whe_… |
url | string | |
events | array of string | |
description | string | null | |
created_at | string | |
disabled_at | string | null | Set when the endpoint is off (by you, or after 3 days of failed deliveries). |
disabled_reason | string | null | |
failing_since | string | null | When deliveries started failing. Cleared by the next success. |
recent_deliveries | array of WebhookDelivery |
#WebhookEndpointList
| Field | Type | Description |
|---|---|---|
object | "list" | |
data | array of WebhookEndpoint |
#WebhookDelivery
| Field | Type | Description |
|---|---|---|
object | "webhook_delivery" | |
id | string | The delivery id (evt_…), sent as webhook-id. |
event | string WebhookEventType | One of job.completed, job.failed, job.blocked, job.resumed, job.expired, message.completed, message.partial, message.failed, message.needs_input, message.blocked, message.expired. |
status | string | One of pending, delivered, cancelled, dead. |
attempts | integer | |
last_status_code | integer | null | |
last_error | string | null | |
last_response | string | null | The first 4 KB of your endpoint's last response. |
last_ms | integer | null | |
created_at | string | |
delivered_at | string | null | |
next_attempt_at | string | null | |
message_id | string | null |
#RotatedSecret
| Field | Type | Description |
|---|---|---|
object | "webhook_endpoint" | |
id | string | |
secret | string |
#Deleted
| Field | Type | Description |
|---|---|---|
object | "webhook_endpoint" | |
id | string | |
deleted | true |
#JobEvent
| Field | Type | Description |
|---|---|---|
type | string | One of job.completed, job.failed, job.blocked, job.resumed, job.expired. |
timestamp | string | |
data | object | |
object | "job" | |
message_id | string | |
job_id | string | exe_… for work that ran or is queued; ref_… for work refused before it was queued. |
kind | string | One of execution, refusal. |
role | string | follow_up when the message joined work that was already running; its status follows that work. One of execution, follow_up. |
work_id | string | null | The chat the work runs in. |
title | string | null | |
status | string JobStatus | One of running, completed, failed, cancelled, blocked, expired. |
reason | string | null | For blocked, failed, cancelled and expired: out_of_credits, hosting_fees, too_many_running, execution_failed, stopped, queue_expired, watch_expired, result_unknown. |
final | boolean | false while running, and for queued work waiting for credits (it can still run). |
queued | boolean | null | true for work that is queued and waiting for credits; otherwise null. |
resume_by | string | null | For queued work waiting for credits, the deadline to add them (24 hours after it was queued). |
link | string | null | The work in the SuperCool app. |
started_at | string | null | |
settled_at | string | null | |
files | array of File |
#MessageEvent
| Field | Type | Description |
|---|---|---|
type | string | One of message.completed, message.partial, message.failed, message.needs_input, message.blocked, message.expired. |
timestamp | string | |
data | Message | A message you sent and everything that came of it. |
#Error
Every API error has this shape.
| Field | Type | Description |
|---|---|---|
error required | string ErrorCode | One of bad_request, invalid_request, empty_message, too_long, bad_cursor, bad_url, bad_events, bad_size, too_many, unauthorized, not_found, idempotency_conflict, poll_in_progress, too_many_endpoints, not_pending, not_uploaded, changed, upload_not_ready, too_large, executable_refused, rate_limited, internal, unavailable. |
message required | string | What went wrong, for people. |
request_id required | string | The request's id (req_…), also in the Request-Id header. |
docs required | string | A link to this error code's documentation. |
problems | array of object | With invalid_request: what doesn't match, one entry per problem (up to 10). |
field | string | null | The dotted location of the field (like files.0.url or wait), or null for the body as a whole. |
problem | string | |
cursor | string | With poll_in_progress: the cursor to use when you call again. |
#ErrorCode
A stable, machine-readable error code. Each one is documented at https://supercool.com/docs/api/errors#<code>.
string One of bad_request, invalid_request, empty_message, too_long, bad_cursor, bad_url, bad_events, bad_size, too_many, unauthorized, not_found, idempotency_conflict, poll_in_progress, too_many_endpoints, not_pending, not_uploaded, changed, upload_not_ready, too_large, executable_refused, rate_limited, internal, unavailable.
#CrmError
A CRM API error: FastAPI's {"detail": …}.
| Field | Type | Description |
|---|---|---|
detail required | string | What went wrong, for people. |
#CrmValidationError
A body that doesn't match the endpoint's schema (422).
| Field | Type | Description |
|---|---|---|
detail required | array of object | One entry per problem. |
type | string | The kind of problem, like missing. |
loc | array of string | integer | Where, like ["body", "title"]. |
msg | string | The problem, for people. |
input | any | The value that was sent. |
#CrmRefusal
A text or email the CRM's rules refused (422). Nothing was sent or queued.
| Field | Type | Description |
|---|---|---|
detail required | object | |
reason required | string | Machine-readable: no_consent, dnd, suppressed_owner, suppressed_global, paused, cap, credits, no_phone, landline, quiet_hours, no_number, unregistered, suspended, releasing, unpaid, kill_switch, too_long, media, no_email, no_channel, unsubscribed, bounced. See refusal reasons. |
message required | string | What happened, for people. |
#CrmOk
| Field | Type | Description |
|---|---|---|
ok required | true |
#CrmAccount
An API key, its owner, and the CRM it reaches.
| Field | Type | Description |
|---|---|---|
ok required | true | |
user_id required | string | The key owner's user id. |
email | string | null | The key owner's email address. |
workspace_id required | string | null | The team id, or null for the owner's personal CRM. |
team_name | string | null | The team's name, or null for a personal CRM. |
role required | string | The key owner's role in this CRM. One of admin, member. |
key required | object | |
id | string | The key's id. |
name | string | null | The key's nickname. |
hint | string | null | Its first and last characters, like sc_key_9fK2…x7Qa. |
label required | string | A name for the connection, like [email protected] · Rivera Studio. |
#CrmTrigger
| Field | Type | Description |
|---|---|---|
key | string | The trigger key to subscribe with, like new_contact. |
label | string | Its name, like New Contact. |
noun | string | What it's about, like Contact. |
types | array of string | The CRM event types it fires on, like contact.created. |
#CrmTriggerList
| Field | Type | Description |
|---|---|---|
items required | array of CrmTrigger |
#CrmHookCreate
| Field | Type | Description |
|---|---|---|
event required | string | A trigger key (GET /zapier/triggers), like new_contact. |
target_url required | string | A public http or https URL. Private and local addresses are refused. Up to 2048 characters. |
#CrmHookDelete
Send id, or target_url (optionally with event).
| Field | Type | Description |
|---|---|---|
id | string | The subscription's id (zh_…). |
target_url | string | Remove every subscription to this URL. |
event | string | With target_url, only the subscription for this trigger. |
#CrmHook
A REST hook subscription. It belongs to the CRM, not to the key.
| Field | Type | Description |
|---|---|---|
id | string | zh_… |
key_id | string | The API key that created it. If that key is revoked, the subscription is removed at its next event. |
event | string | The trigger key. |
label | string | The trigger's name. |
target_url | string | |
target_host | string | null | |
key_name | string | null | The creating key's nickname. |
key_hint | string | null | The creating key's first and last characters. |
deliveries | integer | Events delivered. |
failures | integer | Events that failed for good. |
last_delivery_at | string | null | |
last_status | integer | null | The HTTP status of the latest attempt. |
last_error | string | null | The latest attempt's error, if it failed. |
created_at | string | |
updated_at | string |
#CrmHookList
| Field | Type | Description |
|---|---|---|
items required | array of CrmHook |
#CrmEvent
What a REST hook receives, and what GET /zapier/perform-list/{event} returns: one CRM event and its contact. POSTed with X-SuperCool-Event and X-SuperCool-Delivery headers; see Delivery.
| Field | Type | Description |
|---|---|---|
id | string | The event id (ev_…). The same on every retry; use it to drop duplicates. |
trigger | string | The trigger key the subscription is for. |
type | string | The CRM event type, like contact.created. |
label | string | The event type's name, like Contact created. |
occurred_at | string | |
contact_id | string | null | |
contact | object | null | The contact, flattened for field mapping. |
id | string | |
email | string | null | |
phone | string | null | |
first_name | string | null | |
last_name | string | null | |
full_name | string | null | |
company | string | null | |
address | string | null | |
city | string | null | |
state | string | null | |
postal_code | string | null | |
country | string | null | |
website | string | null | |
source | string | null | |
owner_user_id | string | null | |
fields | object | Custom field values by key. |
tag_ids | array of string | |
tags | array of string | Tag names. |
dnd | object | email, sms, calls, all (booleans), reason, at. |
consent | object | email and sms: each consent status, like explicit, or null. |
created_at | string | |
updated_at | string | |
data | object | What happened. The fields depend on the trigger; see the trigger catalogue. |
ref | object | Ids of related records, like opportunity_id, appointment_id, task_id or form_id. |
actor | object | Who did it: kind (user, api_key, webhook or system) and id. |
#CrmEventList
| Field | Type | Description |
|---|---|---|
items required | array of CrmEvent |
#CrmContactLookup
Names an existing contact: by contact_id (or contact) as an id, then as an email address or phone number, then by email, then by phone. No match is 404 Contact not found.
| Field | Type | Description |
|---|---|---|
contact_id | string | The contact's id (ct_…). |
contact | string | An id, email address or phone number. |
email | string | |
phone | string |
#CrmTags
Tag names, as a list or one comma-separated string (up to 50, 80 characters each).
array of string or string
#CrmContactWrite
A contact's fields. For create or update, email or phone is required and finds the contact. For update, contact_id or contact (or email / phone) finds it.
| Field | Type | Description |
|---|---|---|
contact_id | string | Update only. The contact's id. |
contact | string | Update only. An id, email address or phone number. |
email | string | |
phone | string | Stored in E.164. US numbers without a country code get +1. |
first_name | string | |
last_name | string | |
company | string | |
address | string | |
city | string | |
state | string | |
postal_code | string | |
country | string | |
website | string | |
owner_user_id | string | A team member's user id (GET /zapier/team). |
tags | array of string or string CrmTags | Tag names, as a list or one comma-separated string (up to 50, 80 characters each). |
fields | object | Custom field values by key (GET /zapier/fields). Create or update ignores unknown keys; update refuses them. |
consent | boolean | Create or update only. true records explicit email consent. Otherwise email consent is imported_unknown, unless already explicit. |
sms_consent | boolean | Create or update only. true records explicit SMS consent, if the contact has a phone number. |
#CrmContact
A CRM contact. Other fields may be present, like attribution.
| Field | Type | Description |
|---|---|---|
id | string | ct_… |
email | string | null | |
phone | string | null | E.164. |
first_name | string | null | |
last_name | string | null | |
company | string | null | |
address | string | null | |
city | string | null | |
state | string | null | |
postal_code | string | null | |
country | string | null | |
website | string | null | |
display_name | string | The full name, else the email, else the phone. |
tags | array of string | Tag ids (tag_…). |
fields | object | Custom field values by key. |
source | string | Where it came from, like zapier, lead_api, form, import or manual. |
owner_user_id | string | null | |
status | string | One of active, unsubscribed, bounced, blocked. |
dnd | object | Do Not Disturb: email, sms, calls, all (booleans), reason, at. |
consent | object | email and call, each {status, source, at}. Send Email needs email consent explicit. |
last_activity_at | string | |
created_at | string | |
updated_at | string |
#CrmContactResult
The contact's fields at the top level, plus ok (and created from create or update).
| Field | Type | Description |
|---|---|---|
ok | true | |
created | boolean | Create or update only. true if the contact is new. |
id | string | ct_… |
email | string | null | |
phone | string | null | E.164. |
first_name | string | null | |
last_name | string | null | |
company | string | null | |
address | string | null | |
city | string | null | |
state | string | null | |
postal_code | string | null | |
country | string | null | |
website | string | null | |
display_name | string | The full name, else the email, else the phone. |
tags | array of string | Tag ids (tag_…). |
fields | object | Custom field values by key. |
source | string | Where it came from, like zapier, lead_api, form, import or manual. |
owner_user_id | string | null | |
status | string | One of active, unsubscribed, bounced, blocked. |
dnd | object | Do Not Disturb: email, sms, calls, all (booleans), reason, at. |
consent | object | email and call, each {status, source, at}. Send Email needs email consent explicit. |
last_activity_at | string | |
created_at | string | |
updated_at | string |
#CrmContactList
| Field | Type | Description |
|---|---|---|
items required | array of CrmContact | The contact, or empty. |
#CrmTagsChange
A contact (see CrmContactLookup) and the tags to add or remove.
| Field | Type | Description |
|---|---|---|
contact_id | string | The contact's id (ct_…). |
contact | string | An id, email address or phone number. |
email | string | |
phone | string | |
tags required | array of string or string CrmTags | Tag names, as a list or one comma-separated string (up to 50, 80 characters each). |
#CrmOpportunityCreate
A contact (see CrmContactLookup) and the opportunity.
| Field | Type | Description |
|---|---|---|
contact_id | string | The contact's id (ct_…). |
contact | string | An id, email address or phone number. |
email | string | |
phone | string | |
pipeline_id | string | The pipeline's id (pl_…). With neither this nor pipeline, the default pipeline. |
pipeline | string | The pipeline's id or name. |
stage_id | string | The stage's id (st_…) or name. With neither this nor stage, the first stage. |
stage | string | The stage's id or name (any case). |
name | string | Defaults to the contact's name. |
value | number or string | A number is in minor units (150000 is 1,500.00); a string is in major units ("1,500.00"). |
currency | string | A 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 | array of string or string CrmTags | Tag names, as a list or one comma-separated string (up to 50, 80 characters each). |
fields | object | Opportunity custom field values by key. |
expected_close_at | string | YYYY-MM-DD. |
allow_duplicate | boolean | Allow a second open opportunity for the contact in this pipeline. |
#CrmOpportunityMove
At least one of a stage, a pipeline or a status.
| Field | Type | Description |
|---|---|---|
stage_id | string | The new stage's id or name. |
stage | string | The new stage's id or name (any case). |
pipeline_id | string | Move to this pipeline (its first stage unless a stage is given). |
pipeline | string | The pipeline's id or name. |
status | string | One of open, won, lost, abandoned. |
lost_reason | string | With status lost. |
#CrmOpportunity
A potential sale on one contact, in one stage of one pipeline. Other fields are included too, like followers, stage_entered_at and days_in_stage.
| Field | Type | Description |
|---|---|---|
id | string | opp_… |
pipeline_id | string | |
stage_id | string | |
contact_id | string | |
name | string | |
status | string | One of open, won, lost, abandoned. |
value | integer | In minor units (cents). |
currency | string | Lower-case 3-letter code. |
source | string | null | |
owner_user_id | string | null | |
tags | array of string | Tag ids. |
fields | object | |
lost_reason | string | null | |
expected_close_at | string | null | |
created_at | string | |
updated_at | string |
#CrmTaskCreate
The task, and optionally its contact (see CrmContactLookup).
| Field | Type | Description |
|---|---|---|
contact_id | string | The contact's id (ct_…). |
contact | string | An id, email address or phone number. |
email | string | |
phone | string | |
title required | string | Longer titles are cut to 200 characters. |
description | string | |
opportunity_id | string | |
assigned_to | string | A team member's user id. |
due_at | string | An exact time. Wins over due_date. |
due_date | string | YYYY-MM-DD. Without due_time, due all day. |
due_time | string | HH:MM. |
timezone | string | The IANA zone of due_date and due_time. Defaults to the assignee's zone, else the CRM's, else UTC. |
#CrmTask
| Field | Type | Description |
|---|---|---|
id | string | tsk_… |
title | string | |
description_html | string | null | |
contact_id | string | null | |
opportunity_id | string | null | |
assigned_to | string | null | |
due_at | string | null | |
due_date | string | null | |
due_time | string | null | |
all_day | boolean | |
timezone | string | null | |
status | string | open for a new task. |
origin | object | {"kind": "manual", "reason": "Created by Zapier"} for tasks this API creates. |
created_at | string | |
updated_at | string |
#CrmEnrollment
| Field | Type | Description |
|---|---|---|
ok | boolean | true if the contact was enrolled. |
reason | string | One of enrolled, already_active, no_reentry, not_published. |
enrollment_id | string | null | wfe_…, when enrolled. |
workflow_id | string | |
workflow_name | string | null | |
contact_id | string |
#CrmTextSend
A contact (see CrmContactLookup) and the text.
| Field | Type | Description |
|---|---|---|
contact_id | string | The contact's id (ct_…). |
contact | string | An id, email address or phone number. |
email | string | |
phone | string | |
text | string | Required (or message). Cut at 1,600 characters; over 10 segments with the opt-out footer is refused (too_long). |
message | string | Same as text. |
media | array of string | One image file already stored in SuperCool (its storage key), up to 1 MB, for MMS. Anything else is refused (media). |
#CrmEmailSend
A contact (see CrmContactLookup) and the email.
| Field | Type | Description |
|---|---|---|
contact_id | string | The contact's id (ct_…). |
contact | string | An id, email address or phone number. |
email | string | |
phone | string | |
subject | string | Required. Cut at 300 characters. |
html | string | The HTML body. html, body or text is required. |
body | string | Same as html. |
text | string | A plain-text body. Without html, it also becomes the HTML body. |
#CrmSent
A text or email that was sent, or handed to the carrier or provider.
| Field | Type | Description |
|---|---|---|
ok | true | |
contact_id | string | |
id | string | The message id (cmsg_…). |
status | string | accepted, uncertain or failed; a text can also be refused (the carrier says the number opted out). |
kind | string | sms or email. |
conversation_id | string | |
segments | integer | Texts only. |
credits | integer | What the send cost. |
error | string | null | |
created_at | string |
#CrmPipelineList
| Field | Type | Description |
|---|---|---|
items required | array of object | |
id | string | pl_… |
name | string | |
stages | array of object | In order. |
id | string | st_… |
name | string | |
position | integer |
#CrmWorkflowList
| Field | Type | Description |
|---|---|---|
items required | array of object | Published workflows. |
id | string | wf_… |
name | string | |
status | string |
#CrmTagList
| Field | Type | Description |
|---|---|---|
items required | array of object | |
id | string | tag_… |
name | string |
#CrmTeamList
| Field | Type | Description |
|---|---|---|
items required | array of object | |
id | string | The member's user id. |
name | string | null | |
email | string | null | |
role | string | One of admin, member. |
#CrmFieldList
| Field | Type | Description |
|---|---|---|
items required | array of object | |
key | string | What fields takes. |
label | string | |
type | string | One of text, number, date, select, multiselect, checkbox, url. |
#CrmLeadCreate
A lead. email or phone is required.
| Field | Type | Description |
|---|---|---|
email | string | |
phone | string | |
first_name | string | |
last_name | string | |
company | string | |
address | string | |
city | string | |
state | string | |
postal_code | string | |
country | string | |
website | string | |
tags | array of string | Tag names to add (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. Otherwise imported_unknown, unless already explicit. |
sms_consent | boolean | true records explicit SMS consent, if the lead has a phone number. |
source | string | Your label for where the lead came from. Kept as lead_source. |
attribution | object | utm_source, utm_medium, utm_campaign, utm_term, utm_content, gclid, fbclid, referrer, landing_url. The first becomes the contact's first touch; each updates the last touch. |
opportunity | object | Add the lead to a pipeline: pipeline_id or pipeline (name), stage_id, value (minor units if a number, major if a string), name. If it can't be created, the lead is still saved. |
workspace_id | string | The team id, if you don't send X-Workspace-Id. |
#CrmLeadCreated
| Field | Type | Description |
|---|---|---|
ok | true | |
created | boolean | true if the contact is new. |
contact | CrmContact | A CRM contact. Other fields may be present, like attribution. |