Code examples
Five common integration tasks, each with a real request and response.
Every request/response body on this page was run against a live Threadliner API instance and copied verbatim — not hand-typed. See Authentication for how to get an API key, and Error response format for what failures look like.
Create a contact
The one endpoint most integrations start with — everything else (forms, lists, deals) attaches to a contact record.
curl -X POST https://api.threadliner.io/contacts \
-H "Authorization: Bearer tl_live_..." \
-H "Content-Type: application/json" \
-d '{"email":"jane@example.com","firstName":"Jane","lastName":"Doe"}'{
"id": "cms7wqpka00068o3zyl49e2dn",
"email": "jane@example.com",
"firstName": "Jane",
"lastName": "Doe",
"phone": null,
"company": null,
"customFieldValues": {},
"tags": [],
"emailSubscribedStatus": "NONE",
"lastActivityAt": null,
"source": null,
"createdAt": "2026-07-30T19:29:32.650Z",
"updatedAt": "2026-07-30T19:29:32.650Z"
}Full field reference: Create a contact.
Submit a form
Unauthenticated — this is what a form embed itself calls, so it never carries an API
key. Field IDs come from the form's own schema (GET /public/forms/:id); this example
matches a two-field form with IDs email and firstName.
curl -X POST https://api.threadliner.io/public/forms/cms7wtd4w000o8o3za23a9k2s/submit \
-H "Content-Type: application/json" \
-d '{"values":{"email":"lead@example.com","firstName":"Lee"}}'{
"success": true,
"submissionId": "cms7wtd88000s8o3zz79xxpf0"
}More on mapping fields to contact fields and conditional logic: Forms.
Create a deal
Requires an existing pipelineId (list pipelines)
and contactId. stageId is optional — omitted here, so it defaults to the
pipeline's first stage.
curl -X POST https://api.threadliner.io/deals \
-H "Authorization: Bearer tl_live_..." \
-H "Content-Type: application/json" \
-d '{
"pipelineId": "cms7wsqyq000c8o3zyy8rolvj",
"contactId": "cms7wqpka00068o3zyl49e2dn",
"name": "Jane Doe - Annual Plan",
"valueCents": 120000
}'{
"id": "cms7wsr11000i8o3zgli0gkno",
"pipelineId": "cms7wsqyq000c8o3zyy8rolvj",
"stageId": "cms7wsqyq000d8o3z7mvvvbxt",
"contactId": "cms7wqpka00068o3zyl49e2dn",
"name": "Jane Doe - Annual Plan",
"valueCents": 120000,
"status": "OPEN",
"closedAt": null,
"createdAt": "2026-07-30T19:31:07.861Z",
"updatedAt": "2026-07-30T19:31:07.861Z"
}valueCents is in cents (120000 = $1,200.00) — same convention used throughout
CRM.
Register a webhook
eventTypes accepts any of the event types listed here.
Like an API key, the secret is only ever returned in this response — save it, it's
what you use to verify each delivery's signature.
curl -X POST https://api.threadliner.io/webhooks \
-H "Authorization: Bearer tl_live_..." \
-H "Content-Type: application/json" \
-d '{"url":"https://example.com/hooks/threadliner","eventTypes":["contact.created"]}'{
"id": "cms7ws6n8000a8o3zqq4fc4yl",
"url": "https://example.com/hooks/threadliner",
"eventTypes": ["contact.created"],
"enabled": true,
"createdAt": "2026-07-30T19:30:41.444Z",
"secret": "_y4SwpCiP3z0LzSQ5Wn5Db1P0snrcejC"
}Deliveries are single-attempt — there's no automatic retry on failure. See Webhooks for delivery status/error visibility.
Send a campaign
Sends immediately to every confirmed subscriber on the campaign's list — there's no
scheduling. campaignId comes from creating a campaign
first.
curl -X POST https://api.threadliner.io/campaigns/cms7wtteg00108o3zmyt1oi4a/send \
-H "Authorization: Bearer tl_live_..."{ "queued": 1 }queued is the number of confirmed subscribers the campaign was queued to send to. If
the list has no confirmed subscribers yet, this fails instead:
{
"statusCode": 400,
"message": "List has no subscribed recipients",
"error": "Bad Request"
}Last updated on