API Reference.
Schema-true.
A curated reference for the Conduyt endpoints you'll use most — field tables generated from the same schemas the API enforces. The complete, machine-readable catalog of all 831 endpoints is at GET /api/v1/schema/public (no auth) and GET /api/v1/schema/api-catalog.
API Reference
The same endpoints that power the Conduyt web app. Every request requires a Bearer token. All responses return JSON.
Authentication
All requests require a Bearer token in the Authorization header. Generate API keys in Settings → API. Keys are prefixed with cdy_.
curl https://conduyt.app/api/v1/contacts \ -H "Authorization: Bearer cdy_your_api_key" \ -H "Content-Type: application/json"
Errors
Error responses are a flat JSON object: error holds the human-readable message and is always present. code is a stable machine-readable discriminator that some endpoints additionally set; treat it as optional. Rate-limited responses may also carry retryAfter (seconds).
{
"error": "pipelineId: pipelineId must be a UUID — resolve a pipeline NAME first via POST /api/v1/automations/resolve"
}{
"error": "Rate limit exceeded",
"code": "rate_limited",
"retryAfter": 42
}| Status | Description |
|---|---|
400 | Malformed or invalid request body / query — the message names the failing field |
401 | Missing or invalid API key |
402 | Billing inactive for the workspace |
403 | Key lacks the required scope or role |
404 | Resource does not exist in your workspace |
413 | Payload too large (e.g. customFields over 64 KB) |
422 | Semantic validation error (some endpoints) |
429 | Rate limit exceeded — honor retryAfter |
500 | Server error — safe to retry idempotent requests |
Contacts
Manage contacts in your CRM. Contacts represent people your team interacts with: leads, customers, partners.
List all contacts with optional filters and pagination.
| Parameter | Description |
|---|---|
cf | — |
cf[<key>] | — |
assigned_to | Filter by assigned user ID |
company | — |
company_id | — |
created_from | — |
created_to | — |
deal_context_owners | — |
deal_context_stages | — |
deal_context_statuses | — |
exclude_master_status | — |
filters | — |
import_id | — |
include | — |
limit | — |
master_status | — |
min_score | — |
order | — |
page | Page number (default: 1) |
per_page | — |
reachability | — |
search | Search by name, email, or phone |
smartListId | — |
smart_view | — |
sort | — |
source | Filter by lead source |
tag | Filter by tag name |
tags | — |
updated_from | — |
updated_to | — |
{
"data": {
"data": [
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"firstName": "Sarah",
"lastName": "Kim",
"email": "sarah.kim@example.com",
"phone": "+14155552671",
"company": "Acme Corp",
"source": "website",
"tags": [ { "id": "9b2f5c1e-3a4d-4b6c-8d7e-0f1a2b3c4d5e", "name": "enterprise" } ],
"assignedTo": "3f8b1c22-9d4e-4a17-b0c5-6e2f7a8d9b31",
"createdAt": "2026-03-15T10:30:00Z",
"updatedAt": "2026-04-10T14:22:00Z"
}
],
"meta": { "page": 1, "per_page": 50, "total": 342 }
}
}curl -H "Authorization: Bearer cdy_your_api_key" \ "https://conduyt.app/api/v1/contacts?search=sarah&per_page=10"
Create a new contact in the workspace. Every field is individually optional, but at least one of firstName, lastName, email, or phone must be present — an empty identity is a 400.
| Field | Description |
|---|---|
firstNamestringoptional | Contact first name. max 100 chars. nullable |
lastNamestringoptional | Contact last name. max 100 chars. nullable |
emailstringoptional | Email address. max 254 chars. nullable |
phonestringoptional | Phone number (E.164 format). max 30 chars. nullable |
companystringoptional | Company name. max 200 chars. nullable |
companyIdstringoptional | max 100 chars. nullable |
jobTitlestringoptional | max 150 chars. nullable |
sourcestringoptional | Lead source (e.g., website, referral, ad). max 100 chars. nullable |
addressLine1stringoptional | max 200 chars. nullable |
addressLine2stringoptional | max 200 chars. nullable |
citystringoptional | max 100 chars. nullable |
statestringoptional | max 100 chars. nullable |
zipstringoptional | max 20 chars. nullable |
countrystringoptional | max 2 chars. nullable |
timezonestringoptional | max 64 chars. nullable |
legacyStatusstringoptional | max 200 chars. nullable |
masterStatusstringoptional | max 40 chars |
externalBackendbooleanoptional | nullable |
languagestringoptional | max 16 chars. nullable |
doNotContactbooleanoptional | nullable |
doNotContactReasonstringoptional | max 500 chars. nullable |
assignedTostringoptional | User ID to assign this contact to. max 100 chars. nullable |
tagsstring[]optional | Array of tag names to apply. max 100 items. each max 100 chars |
customFieldsobjectoptional | Key-value pairs for custom fields. has conditional cross-field requirements |
attributionobjectoptional | Keys: utm_source (string), utm_medium (string), utm_campaign (string), utm_content (string), utm_term (string), referrer (string), landingUrl (string), pageTitle (string), clickIds (object) |
curl -X POST https://conduyt.app/api/v1/contacts \
-H "Authorization: Bearer cdy_your_api_key" \
-H "Content-Type: application/json" \
-d '{
"firstName": "Sarah",
"lastName": "Kim",
"email": "sarah.kim@acme.com",
"phone": "+14155552671",
"company": "Acme Corp",
"source": "website",
"tags": ["enterprise"]
}'Retrieve a single contact by ID. Returns the full contact object including custom fields and tags.
curl -H "Authorization: Bearer cdy_your_api_key" \ https://conduyt.app/api/v1/contacts/550e8400-e29b-41d4-a716-446655440000
Update a contact. Only include fields you want to change. Unspecified fields are left unchanged.
curl -X PATCH https://conduyt.app/api/v1/contacts/550e8400-e29b-41d4-a716-446655440000 \
-H "Authorization: Bearer cdy_your_api_key" \
-H "Content-Type: application/json" \
-d '{ "company": "Acme Industries", "source": "referral" }'Soft-deletes the contact: the record is flagged deleted and disappears from lists and search, and pending automations and scheduled messages for it are cancelled. Associated notes, tasks and conversations are retained. For actual erasure use the data-retention / GDPR workflow.
curl -X DELETE -H "Authorization: Bearer cdy_your_api_key" \ https://conduyt.app/api/v1/contacts/550e8400-e29b-41d4-a716-446655440000
Attach one or more existing tags to a contact by tag id. Tags are NOT auto-created here — create them first with POST /api/v1/tags, or resolve names to ids via POST /api/v1/automations/resolve. tagIds must be a non-empty array; every id must belong to your workspace.
curl -X POST https://conduyt.app/api/v1/contacts/550e8400-e29b-41d4-a716-446655440000/tags \
-H "Authorization: Bearer cdy_your_api_key" \
-H "Content-Type: application/json" \
-d '{ "tagIds": ["9b2f5c1e-3a4d-4b6c-8d7e-0f1a2b3c4d5e"] }'Remove a specific tag from a contact.
curl -X DELETE -H "Authorization: Bearer cdy_your_api_key" \ https://conduyt.app/api/v1/contacts/550e8400-e29b-41d4-a716-446655440000/tags/6f7a8b9c-0d1e-4f2a-9b43-5d6e7f8091a2
Retired legacy import endpoint. It returns 410 Gone. Use the canonical import job APIs instead.
| Endpoint | Description |
|---|---|
POST /api/v1/imports | Create an import job and upload/import source metadata. |
POST /api/v1/imports/:id/process | Run canonical validation, duplicate handling, re-enrollment controls, and side effects for the job. |
{
"error": "This legacy contacts import endpoint has been retired. Use /api/v1/imports to create an import job and /api/v1/imports/{id}/process for canonical preflight, re-enrollment, validation, and side effects."
}Companies
Manage companies and organizations. Companies can be linked to multiple contacts.
List all companies with optional search and pagination.
| Parameter | Description |
|---|---|
hasOpenDeals | — |
industry | — |
lifecycleStage | — |
order | — |
ownerId | — |
page | Page number (default: 1) |
per_page | — |
search | Search by company name |
size | — |
sort | — |
{
"data": {
"data": [
{
"id": "8e3b4a95-6c7d-4f31-9021-5c9d0e1f2a34",
"name": "Acme Corp",
"domain": "acme.com",
"industry": "Technology",
"size": "51-200",
"contactCount": 12,
"createdAt": "2026-02-10T08:00:00Z"
}
],
"meta": { "page": 1, "per_page": 50, "total": 87 }
}
}Create a new company.
| Field | Description |
|---|---|
namestringrequired | Company name. max 200 chars |
domainstringoptional | Website domain. max 200 chars. nullable |
industrystringoptional | Industry classification. max 100 chars. nullable |
sizeenumoptional | Company size bracket. One of: 1-10, 11-50, 51-200, 201-500, 501-1000, 1001+. nullable |
annualRevenuenumberoptional | nullable |
ownerIduuidoptional | nullable |
parentCompanyIduuidoptional | nullable |
lifecycleStagestringoptional | max 100 chars. nullable |
websitestringoptional | max 500 chars. nullable |
phonestringoptional | max 30 chars. nullable |
addressstringoptional | Street address line. City, state, zip and country are their own top-level fields. max 300 chars. nullable |
citystringoptional | max 100 chars. nullable |
statestringoptional | max 100 chars. nullable |
zipstringoptional | max 20 chars. nullable |
countryoptional | — |
descriptionstringoptional | max 2000 chars. nullable |
customFieldsoptional | — |
Retrieve a single company by ID, including linked contacts.
Update company fields. Only include fields you want to change.
Delete a company. Linked contacts are not deleted but the association is removed.
Deals
Manage deals in your pipeline. Deals represent potential revenue and track through stages from qualification to close.
List all deals with optional filters. Results are ordered by updated_at descending by default.
| Parameter | Description |
|---|---|
assignedTo | Filter by assigned user ID |
assigned_to | — |
contact_id | — |
created_after | — |
created_before | — |
maxValue | — |
minValue | — |
needsAction | — |
order | — |
page | Page number (default: 1) |
per_page | — |
pipeline | — |
pipelineId | Filter by pipeline ID |
pipelineName | — |
pipeline_id | — |
pipeline_name | — |
search | — |
sort | — |
stage | — |
stageId | Filter by stage ID |
stageName | — |
stage_id | — |
stage_name | — |
stale | — |
status | Filter by status: open, won, lost |
summary | — |
tag_ids | — |
value_max | — |
value_min | — |
view | — |
viewId | — |
view_id | — |
{
"data": {
"data": [
{
"id": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
"title": "Northwind Logistics",
"value": 42000,
"currency": "USD",
"pipelineId": "9f1c2d34-5e6a-4b78-9c01-2d3e4f5a6b7c",
"stageId": "1a2b3c4d-5e6f-4a7b-8c9d-0e1f2a3b4c5d",
"probability": 0.55,
"assignedTo": "3f8b1c22-9d4e-4a17-b0c5-6e2f7a8d9b31",
"contactId": "550e8400-e29b-41d4-a716-446655440000",
"companyId": "8e3b4a95-6c7d-4f31-9021-5c9d0e1f2a34",
"status": "open",
"expectedCloseDate": "2026-05-15",
"createdAt": "2026-04-01T14:22:03Z",
"updatedAt": "2026-04-14T09:11:47Z"
}
],
"meta": { "page": 1, "per_page": 50, "total": 156 }
}
}Create a new deal in a pipeline.
| Field | Description |
|---|---|
titlestringrequired | Deal title. max 200 chars |
pipelineIduuidrequired | Pipeline to place the deal in. Resolve a pipeline name to its id with POST /api/v1/automations/resolve. |
stageIduuidrequired | Initial stage within the pipeline |
contactIduuidoptional | Associated contact ID. nullable |
companyIduuidoptional | Associated company ID. nullable |
valueunionoptional | Deal value as a decimal amount (not cents). nullable. has conditional cross-field requirements |
currencystringoptional | ISO 4217 currency code (e.g. GBP). Backend defaults to USD when omitted.. max 10 chars |
statusenumoptional | One of: open, won, lost |
prioritystringoptional | max 30 chars |
sourcestringoptional | Lead source of THIS opportunity (e.g. the campaign/trigger that produced it) — distinct from the contact's first-touch source. max 500 chars. nullable |
lostReasonstringoptional | Reason for a lost/disqualified deal. Required when creating directly into a stage that mandates one; constrained stages only accept their configured options (the 422 lists them).. max 500 chars. nullable |
probabilitynumberoptional | Win probability as a 0–1 fraction (e.g. 0.75 = 75%), not a percentage. range 0–1. nullable |
expectedCloseDatestringoptional | Expected close date (YYYY-MM-DD). max 50 chars. nullable. has conditional cross-field requirements |
appointmentAtISO 8601optional | Appointment date/time for THIS deal (ISO 8601 with offset), pushed by the external booking/AI layer. Drives the missed-appointment clock: appointment time + grace with no human close reads as missed. Send null (or an empty string) to clear.. nullable. has conditional cross-field requirements |
assignedTouuidoptional | User ID to own this deal. nullable |
customFieldsobjectoptional | Key-value pairs for custom fields. has conditional cross-field requirements |
productsobject[]optional | max 50 items. Each item: id (uuid), name (string, required), description (string), quantity (number), unitPrice (union, required), discount (union), tax (union), sortOrder (number) |
curl -X POST https://conduyt.app/api/v1/deals \
-H "Authorization: Bearer cdy_your_api_key" \
-H "Content-Type: application/json" \
-d '{
"title": "Northwind Logistics",
"value": 42000,
"pipelineId": "9f1c2d34-5e6a-4b78-9c01-2d3e4f5a6b7c",
"stageId": "1a2b3c4d-5e6f-4a7b-8c9d-0e1f2a3b4c5d",
"contactId": "550e8400-e29b-41d4-a716-446655440000"
}'Retrieve a single deal by ID, including pipeline, stage, contact, and company details.
Update a deal. Move between stages by changing stageId. Changing status to "won" or "lost" closes the deal.
curl -X PATCH https://conduyt.app/api/v1/deals/7c9e6679-7425-40de-944b-e07fc1f90ae7 \
-H "Authorization: Bearer cdy_your_api_key" \
-H "Content-Type: application/json" \
-d '{ "stageId": "1a2b3c4d-5e6f-4a7b-8c9d-0e1f2a3b4c5d", "value": 48000 }'Pipelines
Configure sales pipelines and their stages. Each workspace can have multiple pipelines for different sales processes.
List all pipelines in the workspace. The body is a plain data array — pagination is exposed via the X-Total-Count, X-Page and X-Per-Page response headers.
{
"data": [
{
"id": "9f1c2d34-5e6a-4b78-9c01-2d3e4f5a6b7c",
"name": "New Business",
"sortOrder": 0,
"createdAt": "2026-01-10T08:00:00Z",
"stages": [
{
"id": "de8bc34e-0477-4cff-a9c5-1cb6dd86cd87",
"name": "New Lead",
"color": "#3B82F6",
"sortOrder": 0,
"isWon": false,
"isLost": false
}
]
}
]
}Create a new pipeline with initial stages.
| Field | Description |
|---|---|
namestringrequired | Pipeline name. max 200 chars |
descriptionstringoptional | max 2000 chars. nullable |
isDefaultbooleanoptional | — |
stagesobject[]optional | Initial stages, in order. max 50 items. Each item: name (string, required), order (number), color (string), isWon (boolean), isLost (boolean) |
Retrieve a pipeline with its stages and summary statistics.
Update pipeline name or settings.
List all stages in a pipeline, ordered by position.
{
"data": [
{ "id": "0a1b2c3d-4e5f-4a6b-8c7d-9e0f1a2b3c4d", "name": "Lead", "color": "#3B82F6", "sortOrder": 0, "isWon": false, "isLost": false },
{ "id": "1a2b3c4d-5e6f-4a7b-8c9d-0e1f2a3b4c5d", "name": "Qualified", "color": "#22F5A4", "sortOrder": 1, "isWon": false, "isLost": false },
{ "id": "2b3c4d5e-6f7a-4b8c-9d0e-1f2a3b4c5d6e", "name": "Proposal", "color": "#956EFA", "sortOrder": 2, "isWon": false, "isLost": false },
{ "id": "3c4d5e6f-7a8b-4c9d-8e1f-2a3b4c5d6e7f", "name": "Negotiation", "color": "#FDC888", "sortOrder": 3, "isWon": false, "isLost": false },
{ "id": "f6071819-2a3b-4c4d-8876-8091a2b3c4d5", "name": "Closed Won", "color": "#22F5A4", "sortOrder": 4, "isWon": true, "isLost": false }
]
}Add a new stage to a pipeline.
| Field | Description |
|---|---|
color | — |
isLost | — |
isWon | — |
name | Stage name |
Update a stage name, position, or probability.
Tasks
Manage tasks assigned to team members. Tasks can be linked to contacts or deals.
List all tasks with optional filters for status, assignee, and due date.
| Parameter | Description |
|---|---|
assignedTo | Filter by assigned user |
assigned_to | — |
contactId | Filter by linked contact |
contact_id | — |
dealId | Filter by linked deal |
deal_id | — |
dueFrom | — |
dueTo | — |
order | — |
overdue | — |
page | — |
per_page | — |
priority | — |
search | — |
sort | — |
status | Filter: todo, in_progress, done. Use overdue=true for overdue tasks |
tab | — |
{
"data": {
"data": [
{
"id": "4d5e6f7a-8b9c-4d0e-9f21-3b4c5d6e7f80",
"title": "Follow up with Sarah Kim",
"description": "Send proposal for Q2 renewal",
"status": "todo",
"priority": "high",
"dueDate": "2026-04-20T17:00:00Z",
"assignedTo": "3f8b1c22-9d4e-4a17-b0c5-6e2f7a8d9b31",
"contactId": "550e8400-e29b-41d4-a716-446655440000",
"dealId": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
"createdAt": "2026-04-15T10:00:00Z"
}
],
"meta": { "page": 1, "per_page": 50, "total": 28 }
}
}Create a new task.
| Field | Description |
|---|---|
titlestringrequired | Task title. max 500 chars |
descriptionstringoptional | Task description. max 5000 chars |
dueDatestringoptional | Due date and time. has conditional cross-field requirements |
priorityenumoptional | Priority: low, medium, high. One of: low, medium, high, urgent |
statusenumoptional | One of: todo, in_progress, done |
assignedTounionoptional | Assigned user ID |
contactIduuidoptional | Link to a contact. nullable |
dealIduuidoptional | Link to a deal. nullable |
Retrieve a single task by ID.
Update a task. Status is one of todo, in_progress, done — set done to complete it.
curl -X PATCH https://conduyt.app/api/v1/tasks/4d5e6f7a-8b9c-4d0e-9f21-3b4c5d6e7f80 \
-H "Authorization: Bearer cdy_your_api_key" \
-H "Content-Type: application/json" \
-d '{ "status": "done" }'Notes
Add notes to contacts and deals. Notes appear in the activity timeline and support rich text.
List notes, optionally filtered by contact or deal.
| Parameter | Description |
|---|---|
contact_id | — |
deal_id | — |
page | — |
per_page | — |
{
"data": {
"data": [
{
"id": "9c0d1e2f-3a4b-4c5d-8e76-8091a2b3c4d5",
"body": "Had a great call. They're interested in the premium tier.",
"contactId": "550e8400-e29b-41d4-a716-446655440000",
"dealId": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
"createdBy": "3f8b1c22-9d4e-4a17-b0c5-6e2f7a8d9b31",
"createdAt": "2026-04-14T15:30:00Z"
}
],
"meta": { "page": 1, "per_page": 50, "total": 5 }
}
}Create a note on a contact or deal.
| Field | Description |
|---|---|
bodystringrequired | has conditional cross-field requirements |
contactIdstringoptional | Attach to a contact. max 100 chars. nullable |
dealIdstringoptional | Attach to a deal. max 100 chars. nullable |
isPinnedbooleanoptional | — |
Retrieve a single note by ID.
Update note content.
Tags
Manage tags for segmenting and organizing contacts.
List all tags in the workspace with contact counts.
{
"data": [
{ "id": "6f7a8b9c-0d1e-4f2a-9b43-5d6e7f8091a2", "name": "enterprise", "color": "#22F5A4", "_count": { "contacts": 45 } },
{ "id": "7a8b9c0d-1e2f-4a3b-8c54-6e7f8091a2b3", "name": "vip", "color": "#956EFA", "_count": { "contacts": 12 } },
{ "id": "8b9c0d1e-2f3a-4b4c-9d65-7f8091a2b3c4", "name": "churned", "color": "#FD88C0", "_count": { "contacts": 8 } }
],
"meta": { "page": 1, "per_page": 50, "total": 15 }
}Create a new tag.
| Field | Description |
|---|---|
color | Hex color code for display |
name | Tag name (unique per workspace) |
Retrieve a single tag with its contact count.
Update tag name or color.
Messages
Send SMS and email messages to contacts. View message history and delivery status.
List messages with optional filters for contact, channel, and direction.
| Parameter | Description |
|---|---|
channel | Filter: sms, email |
contactId | Filter by contact |
direction | Filter: inbound, outbound |
page | — |
per_page | — |
status | — |
{
"data": {
"data": [
{
"id": "0d1e2f3a-4b5c-4d6e-9f87-91a2b3c4d5e6",
"contactId": "550e8400-e29b-41d4-a716-446655440000",
"channel": "sms",
"direction": "outbound",
"body": "Hi Sarah, just following up on our conversation...",
"status": "delivered",
"sentAt": "2026-04-15T11:30:00Z"
}
],
"meta": { "page": 1, "per_page": 50, "total": 234 }
}
}Send a message to a contact via SMS or email.
| Field | Description |
|---|---|
contactIdstringrequired | Recipient contact ID. max 100 chars |
channelenumrequired | Channel: sms or email. One of: sms, email |
directionenumrequired | One of: inbound, outbound |
bodystringrequired | Message body (plain text for SMS, HTML for email). has conditional cross-field requirements |
subjectstringoptional | Email subject (required for email channel). max 500 chars. nullable |
fromNumberstringoptional | max 30 chars. nullable |
toNumberstringoptional | max 30 chars. nullable |
fromEmailstringoptional | max 254 chars. nullable |
toEmailstringoptional | max 254 chars. nullable |
replyTostringoptional | max 254 chars. nullable |
ccunionoptional | nullable |
bccunionoptional | nullable |
bodyHtmlstringoptional | nullable |
providerstringoptional | max 50 chars. nullable |
providerIdstringoptional | max 200 chars. nullable |
metadataunknownoptional | — |
statusstringoptional | max 30 chars |
scheduledAtISO 8601optional | nullable |
curl -X POST https://conduyt.app/api/v1/messages \
-H "Authorization: Bearer cdy_your_api_key" \
-H "Content-Type: application/json" \
-d '{
"contactId": "550e8400-e29b-41d4-a716-446655440000",
"channel": "sms",
"direction": "inbound",
"body": "Sounds good, send the proposal over."
}'direction is required. A non-draft outbound SMS is rejected here with a 400 — send live SMS through POST /api/v1/messages/sms/send so provider delivery and compliance checks run.
Send a live SMS to a contact through the account's Twilio line (default) or, with transport: "project_blue", from the calling user's own Project Blue iPhone line — iMessage when the lead is on Apple, SMS otherwise. Compliance checks (opt-outs, DNC, verified landlines) run before anything leaves; a Project Blue send also reserves one request from the account key's shared per-minute budget first. The response carries the stored message with its provider and status. idempotencyKey makes retries safe on the Conduyt side on every path — Twilio, an external provider chosen with smsProviderId, or Project Blue — but only the Project Blue transport carries it through to the vendor, so only there is delivery at-most-once.
| Field | Description |
|---|---|
contactIduuidrequired | Recipient contact ID — the contact must have a phone number and no opt-out |
bodystringoptional | SMS body, 1–1600 characters. max 1600 chars |
fromNumberstringoptional | Twilio only: an account-owned number or agent DID; ignored for project_blue (the line is the sender). max 30 chars |
toNumberstringoptional | max 30 chars |
smsProviderIduuidoptional | Twilio only: route through a configured external SMS provider; a 400 transport_conflict alongside project_blue |
transportenumoptional | twilio (default) or project_blue — the caller must have a line assigned in Settings → Project Blue (422 project_blue_line_missing otherwise). One of: twilio, project_blue, whatsapp |
whatsappContentSidstringoptional | — |
whatsappContentVariablesobjectoptional | — |
idempotencyKeystringoptional | Your operation key (8–200 chars): send the SAME key when retrying a request whose outcome you did not see. A settled key answers with the stored message and never sends twice; a throttled or unconfirmed one re-sends the same message (exactly one retry at a time; a concurrent one is a 409 send_in_progress); a key reused for a different message (other contact, text, media, sender line or provider) is a 409 operation_key_conflict — nothing is sent. At-most-once DELIVERY holds for project_blue only (the vendor sees the same idempotency key); Twilio has no such key, so re-sending after an unconfirmed Twilio outcome can deliver twice. 8–200 chars |
metadataunknownoptional | — |
curl -X POST https://conduyt.app/api/v1/messages/sms/send \
-H "Authorization: Bearer cdy_your_api_key" \
-H "Content-Type: application/json" \
-d '{
"contactId": "550e8400-e29b-41d4-a716-446655440000",
"body": "Hi Sarah, this is Jordan — got a minute to talk about the proposal?",
"transport": "project_blue",
"idempotencyKey": "draft-8f3c2a-attempt-1"
}'{
"data": {
"id": "c1d2e3f4-0000-5000-8000-000000000001",
"status": "sent",
"provider": "project_blue",
"fromNumber": "+15513285489",
"toNumber": "+17185552539",
"metadata": { "service": "iMessage", "lineId": "fdac230c-6228-4560-817f-03378a7c964e" }
}
}Project Blue limits: Conduyt enforces the account key's 60 requests per minute (shared with reply polling) before the wire; the vendor enforces its own per-line pace (about 15 messages per 10 minutes) and answers a throttle with a 429. Either way the message settles as a transient failure and the same idempotencyKey re-sends it a moment later.
Project Blue
A third messaging provider next to Twilio: each agent gets an iPhone line (iMessage + SMS) from your Project Blue account. Connect the account key once (Settings → Project Blue or the API below), then assign one line per agent. Replies come back into the conversation automatically — the line log is reconciled every minute, so nothing depends on a webhook.
Connection status plus this account's line assignments. status is connected or not_configured; the stored key is always masked.
{
"data": {
"provider": "project_blue",
"status": "connected",
"apiKey": "proj_********1a2b",
"assignments": [
{ "lineId": "fdac230c-6228-4560-817f-03378a7c964e", "phoneNumber": "+15513285489", "label": "Sales iPhone",
"user": { "id": "8e1b6c2a-1111-4111-8111-111111111111", "firstName": "Jordan", "lastName": "Tate", "email": "jordan@example.com", "isActive": true } }
]
}
}Store the account's Project Blue API key. The key is verified with the vendor before it is stored: a rejected key is a 422 key_rejected, a vendor outage a 502 vendor_unavailable, and nothing is persisted in either case. Replacing a key releases any line assignments the new key cannot see. Requires settings:edit.
| Field | Description |
|---|---|
apiKeystringrequired | The Project Blue API key (proj_…); never echoed back. max 512 chars |
Forget the key and release every agent's line together. Sends with transport: "project_blue" answer 422 project_blue_not_configured afterwards.
The live line inventory read from the vendor, merged with this account's assignments. Assignments the vendor no longer lists come back as orphaned. 409 not_configured when no key is connected, 422 key_rejected when the vendor no longer accepts the stored key, 502 vendor_unavailable when it cannot be reached.
{
"data": {
"lines": [
{ "lineId": "fdac230c-6228-4560-817f-03378a7c964e", "phoneNumber": "+15513285489", "name": "Sales iPhone",
"assignedTo": { "userId": "8e1b6c2a-1111-4111-8111-111111111111", "name": "Jordan Tate" } }
],
"orphaned": []
}
}Give a line to an agent, or release it with userId: null. One line per agent and one agent per line: the line's previous holder and the agent's previous line are released in the same step and reported as released. The inventory is re-read from the vendor first, so a line deleted there is a 404 line_not_found; the agent must be an active member of the account (404 user_not_found); a key replaced mid-assignment is 409 credentials_changed, a concurrent move of the same line or agent 409 conflict — both retryable.
| Field | Description |
|---|---|
userIduuidrequired | The agent's user ID, or null to release the line. nullable |
curl -X PUT https://conduyt.app/api/v1/settings/project-blue/lines/:lineId \
-H "Authorization: Bearer cdy_your_api_key" \
-H "Content-Type: application/json" \
-d '{ "userId": "8e1b6c2a-1111-4111-8111-111111111111" }'Conversations
View threaded conversation history for contacts, across SMS and email channels.
List all conversation threads with latest message preview.
{
"data": {
"data": [
{
"contact": {
"id": "550e8400-e29b-41d4-a716-446655440000",
"firstName": "Sarah",
"lastName": "Kim",
"email": "sarah.kim@example.com",
"phone": "+14155552671",
"assignedTo": "3f8b1c22-9d4e-4a17-b0c5-6e2f7a8d9b31"
},
"lastMessage": {
"body": "Thanks, I'll review the proposal tonight.",
"channel": "sms",
"direction": "inbound",
"createdAt": "2026-04-15T18:45:00Z"
},
"conversationState": "awaiting_reply",
"sla": { "state": "ok" },
"unreadCount": 1,
"messageCount": 14
}
],
"meta": { "page": 1, "per_page": 50, "total": 89 }
}
}Get the full conversation thread for a specific contact, with all messages across channels.
Calendars
Manage calendars and appointments. Create booking links, schedule meetings, and track availability.
List all calendars in the workspace.
Create a new calendar with availability settings.
| Field | Description |
|---|---|
description | — |
isDefault | — |
name | Calendar name |
timezone | IANA timezone (e.g., America/New_York) |
Retrieve calendar details and availability settings.
Update calendar settings.
List appointments for a calendar with optional date range filter.
| Parameter | Description |
|---|---|
assigned_to | — |
contact_id | — |
page | — |
per_page | — |
start_after | — |
start_before | — |
status | Filter: scheduled, completed, cancelled |
{
"data": {
"data": [
{
"id": "6d7e8f90-91a2-43c4-9fed-f708192a3b4c",
"calendarId": "e5f60718-192a-4b3c-9765-7f8091a2b3c4",
"title": "Discovery Call - Sarah Kim",
"startTime": "2026-04-20T14:00:00Z",
"endTime": "2026-04-20T14:30:00Z",
"contactId": "550e8400-e29b-41d4-a716-446655440000",
"status": "scheduled",
"notes": "Discuss Q2 renewal options"
}
],
"meta": { "page": 1, "per_page": 50, "total": 12 }
}
}Schedule a new appointment on a calendar.
| Field | Description |
|---|---|
assignedTo | — |
contactId | Link to a contact |
description | — |
endTime | End date and time |
location | — |
metadata | — |
startTime | Start date and time |
status | — |
title | Appointment title |
Custom Fields
Define custom fields for contacts, deals, and companies. Fields support text, multi-line text, phone, email, URL, formatted number, date, date/time, dropdown, radio, multi-select, checkbox, and optional URL/email domain rules.
List all custom field definitions.
| Parameter | Description |
|---|---|
entityType | — |
page | — |
per_page | — |
{
"data": [
{
"id": "3a4b5c6d-7e8f-4091-8cba-c4d5e6f70819",
"label": "Lead Score",
"fieldKey": "lead_score",
"fieldType": "number",
"entityType": "contact",
"isRequired": false,
"options": null
},
{
"id": "4b5c6d7e-8f90-41a2-9dcb-d5e6f708192a",
"label": "Industry",
"fieldKey": "industry",
"fieldType": "select",
"entityType": "company",
"isRequired": false,
"options": ["Technology", "Healthcare", "Finance", "Retail", "Other"]
}
],
"meta": { "page": 1, "perPage": 50, "total": 8, "totalPages": 1 }
}Define a new custom field.
| Field | Description |
|---|---|
entityTypeenumrequired | One of: contact, deal, company |
fieldKeystringrequired | — |
labelstringrequired | max 200 chars |
fieldTypeenumrequired | One of: text, textarea, number, date, datetime, url, select, radio, multiselect, boolean, phone, email |
sectionstringoptional | max 120 chars. nullable |
optionsunionoptional | Use {"{ choices: [...] }"} for select/radio/multiselect fields, numberFormat/currencyCode for number fields, and allowedDomains/blockedDomains for URL/email fields. Percentage values are stored as entered: 50 displays as 50%.. nullable |
isRequiredbooleanoptional | — |
sortOrdernumberoptional | range 0–100000 |
Update a custom field definition. Changing type is not allowed if data exists.
Delete a custom field definition. When existing values or dependencies would be affected, the first call answers 409 with an impact preflight (value and dependency counts); retry with the confirmation body to proceed. Value cleanup runs asynchronously — the 200 response hands you a cleanup job, not a completed purge.
| Field | Description |
|---|---|
confirmImpact | Set true to confirm the deletion after reviewing the 409 preflight |
expectedDependencyCount | The dependency count from the preflight — must still match |
expectedValueCount | The value count from the preflight — must still match at delete time |
{
"data": {
"id": "3a4b5c6d-7e8f-4091-8cba-c4d5e6f70819",
"deleted": true,
"cleanupJobId": "5c6d7e8f-9012-4a3b-8dcb-e6f708192a3b",
"valuesQueuedForCleanup": 128,
"dependencySummary": []
}
}Webhooks
Register webhook endpoints to receive real-time event notifications. Webhooks are signed with HMAC-SHA256.
List all registered webhook endpoints.
{
"data": {
"data": [
{
"id": "9091a2b3-c4d5-46f7-a210-2a3b4c5d6e7f",
"url": "https://your-app.com/webhooks/conduyt",
"events": ["contact.created", "deal.stage_changed"],
"isActive": true,
"description": "Production sync",
"createdAt": "2026-04-01T10:00:00Z",
"lastFiredAt": "2026-04-15T14:22:00Z",
"failCount": 0
}
],
"meta": { "page": 1, "per_page": 50, "total": 8 }
}
}Register a new webhook endpoint.
| Field | Description |
|---|---|
description | Internal label for the endpoint |
events | Array of event names to subscribe to |
isActive | Whether the webhook is active (default: true) |
url | HTTPS endpoint URL |
curl -X POST https://conduyt.app/api/v1/webhooks/manage \
-H "Authorization: Bearer cdy_your_api_key" \
-H "Content-Type: application/json" \
-d '{
"url": "https://your-app.com/webhooks/conduyt",
"events": ["contact.created", "deal.created", "deal.stage_changed"],
"isActive": true
}'Retrieve webhook details including delivery history.
Update webhook URL, events, or active status.
Archive a webhook registration and cancel pending deliveries for that endpoint.
Send a test payload to the webhook URL to verify it is receiving and processing events correctly.
{
"success": true,
"statusCode": 200,
"responseTime": 142,
"event": "test.ping"
}List recent delivery attempts for one endpoint. Payload fields with contact PII are redacted unless the caller has full contact visibility.
Replay failed or selected webhook deliveries. Test and replay actions require an active account and owner/admin access.
Users
Manage team members in the workspace. Invite new users, assign roles, and remove access.
List all users in the workspace.
{
"data": {
"data": [
{
"id": "3f8b1c22-9d4e-4a17-b0c5-6e2f7a8d9b31",
"name": "David Park",
"email": "dp@conduyt.app",
"role": "admin",
"status": "active",
"lastActiveAt": "2026-04-15T16:30:00Z",
"createdAt": "2026-01-01T00:00:00Z"
}
],
"meta": { "page": 1, "per_page": 50, "total": 8 }
}
}Send an invitation email to add a new user to the workspace.
| Field | Description |
|---|---|
email | Email address to invite |
extension | — |
firstName | — |
first_name | — |
lastName | — |
last_name | — |
permissions | — |
phone | — |
role | Role: member (default) or admin. Any other value, including viewer, is rejected with 422. |
Retrieve a single user by ID.
Update a user's role or name. Requires admin scope.
Remove a user from the workspace. Their assigned contacts and deals are unassigned.
Automations
Manage n8n-powered automations. Register webhook triggers and fire custom events into your workflows.
List all automation configurations.
{
"data": [
{
"id": "5c6d7e8f-9091-42b3-8edc-e6f708192a3b",
"name": "New Lead Notification",
"trigger": "contact.created",
"webhookUrl": "https://n8n.conduyt.app/webhook/abc123",
"active": true,
"lastTriggered": "2026-04-15T12:00:00Z",
"runCount": 342
}
]
}Create a new automation with a webhook trigger.
| Field | Description |
|---|---|
actions | — |
description | — |
folderId | — |
graph | — |
graphVersion | — |
kind | — |
n8nWebhookUrl | — |
n8nWorkflowId | — |
name | Automation name |
nodes | — |
schedule | — |
scheduleTimezone | — |
startNodeId | — |
trigger | Event that triggers this automation |
triggerConditions | — |
triggerEvent | — |
Retrieve automation details and run history.
List every trigger event automations can listen for, with its payload description. Use this to discover valid trigger values before creating an automation.
| Parameter | Description |
|---|---|
kind | Filter to events available for one runner kind: native or n8n. Omit for all. |
curl -H "Authorization: Bearer cdy_your_api_key" \ "https://conduyt.app/api/v1/automations/events?kind=native"
Bulk Operations
Batch update, delete, or tag records. Every bulk endpoint is synchronous and returns its result immediately. Only update gives a per-row result list; tag, untag, edit, delete and DNC return aggregate counts. Limits are per endpoint: 100 records for update, tag, untag, delete and DNC; 500 for edit.
Apply a DIFFERENT set of fields to each contact, one object per record. To apply the SAME change to many contacts, use /api/v1/bulk/contacts/edit instead.
| Field | Description |
|---|---|
updates | One object per contact, each with its id plus the fields to change (max 100 per request). Allowed fields: firstName, lastName, email, phone, company, jobTitle, source, customFields, assignedTo, optedOutSms, optedOutEmail. |
curl -X POST https://conduyt.app/api/v1/bulk/contacts/update \
-H "Authorization: Bearer cdy_your_api_key" \
-H "Content-Type: application/json" \
-d '{
"updates": [
{ "id": "550e8400-e29b-41d4-a716-446655440000", "source": "trade-show-2026", "assignedTo": "3f8b1c22-9d4e-4a17-b0c5-6e2f7a8d9b31" },
{ "id": "660f9500-f39c-42e5-b827-557766551111", "source": "trade-show-2026", "jobTitle": "VP Operations" }
]
}'{
"data": {
"summary": { "total": 2, "succeeded": 1, "failed": 1 },
"results": [
{ "id": "550e8400-e29b-41d4-a716-446655440000", "success": true },
{ "id": "660f9500-f39c-42e5-b827-557766551111", "success": false, "error": "Contact not found" }
]
}
}Batch delete multiple contacts. Returns AGGREGATE counts rather than a per-row list.
| Field | Description |
|---|---|
ids | Array of contact IDs to delete (max 100) |
Apply ONE existing tag to multiple contacts at once. Create the tag first via POST /api/v1/tags — this endpoint takes a tag id, not a name.
| Field | Description |
|---|---|
contactIds | Array of contact IDs (max 100) |
tagId | The id of the tag to apply |
Apply a DIFFERENT set of fields to each deal, one object per record. For the SAME change across many deals use /api/v1/bulk/deals/edit. Answers 200 with a per-row result list.
| Field | Description |
|---|---|
updates | One object per deal, each with its id plus the fields to change (max 100 per request) |
Search
Cross-entity search across contacts, deals, companies, and notes with a single query.
Search across all entities. Results are ranked by relevance and grouped by type.
| Parameter | Description |
|---|---|
cursor | — |
limit | Max results per type (default: 10) |
q | Required. Search query (min 2 characters); omitting it is a 400 |
curl -H "Authorization: Bearer cdy_your_api_key" \ "https://conduyt.app/api/v1/search?q=acme&limit=5"
{
"results": {
"contacts": [
{ "id": "550e8400-e29b-41d4-a716-446655440000", "name": "Sarah Kim", "company": "Acme Corp", "score": 0.95 }
],
"deals": [
{ "id": "3e6f7a8b-9c0d-4e1f-8a21-3c4d5e6f7a82", "name": "Acme Renewal", "value": 24000, "score": 0.88 }
]
},
"totalResults": 6,
"queryTime": 12
}Activities
Audit trail of all actions in the workspace. Every create, update, delete, and login is logged.
List activity entries with optional filters. Ordered by timestamp descending.
| Parameter | Description |
|---|---|
contact_id | — |
deal_id | — |
fromDate | — |
page | — |
per_page | — |
search | — |
toDate | — |
type | — |
{
"data": {
"data": [
{
"id": "7e8f9091-a2b3-44d5-80fe-08192a3b4c5d",
"entityType": "deal",
"entityId": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
"action": "updated",
"changes": { "stageId": { "from": "1a2b3c4d-5e6f-4a7b-8c9d-0e1f2a3b4c5d", "to": "2b3c4d5e-6f7a-4b8c-9d0e-1f2a3b4c5d6e" } },
"userId": "3f8b1c22-9d4e-4a17-b0c5-6e2f7a8d9b31",
"userName": "David Park",
"timestamp": "2026-04-15T14:22:00Z"
}
],
"meta": { "page": 1, "per_page": 50, "total": 1240 }
}
}Billing
Manage subscriptions via Stripe. Billing management requires an owner/admin interactive session and is not available through API-key automation.
Create a Stripe checkout session for first-time billing, or update an existing subscription. Existing subscription changes may be prorated by Stripe immediately.
| Field | Description |
|---|---|
interval | Billing interval: monthly or yearly |
planId | Plan ID: standard, premium, or enterprise |
{
"data": {
"url": "https://checkout.stripe.com/c/pay/cs_live_..."
}
}{
"data": {
"action": "subscription_updated",
"subscriptionId": "sub_1Oa2b3c4d5e6f7g8",
"plan": "premium",
"status": "active"
}
}Generate a Stripe Customer Portal URL for managing the subscription, payment methods, and invoices. Requires an existing Stripe customer.
{
"data": {
"url": "https://billing.stripe.com/p/session/..."
}
}Get the current subscription status, billing health, usage, and recent invoices for the workspace.
{
"data": {
"plan": "standard",
"planName": "Standard",
"billingStatus": "active",
"interval": "monthly",
"currentPeriodEnd": "2026-05-15T00:00:00Z",
"trialDaysRemaining": null,
"hasSubscription": true,
"health": {
"level": "ok",
"title": "Billing is healthy",
"description": "No billing action is needed right now.",
"action": null
},
"usage": {
"contacts": 342,
"pipelines": 5,
"users": 5
},
"invoiceHistory": []
}
}Dashboard
Get aggregated statistics for the workspace dashboard.
Retrieve the workspace dashboard payload: pipeline summary, recent activities, contact count, deals by status, today's tasks and messages, follow-up and stale-deal queues. Sections you lack permission to view come back empty with the matching canView* flag set to false. Takes no query parameters.
{
"data": {
"pipelineSummary": {
"pipeline": { "id": "225e8b10-4e1c-430d-b4ed-7d8d10a9d88b", "name": "Sales Pipeline" },
"stages": [ { "id": "de8bc34e-0477-4cff-a9c5-1cb6dd86cd87", "name": "New Lead", "dealCount": 12, "totalValue": 48000 } ]
},
"recentActivities": [ ... ],
"contactCount": 342,
"dealsByStatus": {
"open": { "count": 42, "totalValue": 1840000 },
"won": { "count": 118, "totalValue": 3120000 }
},
"tasksDueToday": 5,
"messagesToday": 234,
"unreadMessages": 12,
"needsFollowUp": [ ... ],
"staleDeals": [ ... ],
"overdueTasks": [ ... ],
"canViewContacts": true,
"canViewDeals": true
}
}Authentication Endpoints
Session-based authentication for the web app. These endpoints are primarily used by the frontend, not API integrations.
Authenticate with email and password. The session is set as an HttpOnly conduyt_session cookie — no token appears in the JSON body. Headless API integrations should use an API key (Authorization: Bearer cdy_…) instead of this endpoint. When MFA is enforced for the role, the response is an MFA challenge instead of a session.
| Field | Description |
|---|---|
emailstringrequired | User email address |
passwordstringrequired | User password |
accountIdstringoptional | — |
{
"data": {
"user": {
"id": "3f8b1c22-9d4e-4a17-b0c5-6e2f7a8d9b31",
"email": "dp@conduyt.app",
"firstName": "David",
"lastName": "Park"
},
"account": { "id": "7a8fe51a-e1ba-4139-90ea-b5e3fba6a5aa", "name": "Acme Corp" },
"role": "admin",
"accounts": [ ... ],
"mfaSetupRequired": false
}
}{
"data": {
"mfaRequired": true,
"mfaToken": "..."
}
}Create a new account and workspace.
| Field | Description |
|---|---|
accountName | — |
email | Email address |
firstName | — |
lastName | — |
password | Password (min 8 characters) |
Invalidate the current session token.
Get the current authenticated user and workspace details.
{
"user": {
"id": "3f8b1c22-9d4e-4a17-b0c5-6e2f7a8d9b31",
"name": "David Park",
"email": "dp@conduyt.app",
"role": "admin"
},
"workspace": {
"id": "8f9091a2-b3c4-45e6-910f-192a3b4c5d6e",
"name": "Conduyt HQ",
"plan": "standard",
"memberCount": 8
}
}Forms
Lead capture forms with customizable fields and settings. The public submission endpoint requires no authentication, making it ideal for embedding on external websites.
List all forms in the workspace with submission counts.
| Parameter | Description |
|---|---|
include_archived | — |
is_active | — |
page | Page number (default: 1) |
per_page | — |
search | Search by form name |
{
"data": {
"data": [
{
"id": "a1b2c3d4-e5f6-4890-abcd-ef1234567890",
"name": "Website Contact Form",
"description": "Main lead capture form on the homepage",
"fields": [
{ "key": "name", "label": "Full Name", "type": "text", "required": true },
{ "key": "email", "label": "Email", "type": "email", "required": true },
{ "key": "phone", "label": "Phone", "type": "phone", "required": false },
{ "key": "message", "label": "Message", "type": "textarea", "required": false }
],
"redirectUrl": "https://ritualandglass.com/thank-you",
"settings": { "tags": ["gallery-inquiry"] },
"submissionCount": 247,
"createdAt": "2026-02-10T09:00:00Z",
"updatedAt": "2026-04-18T11:30:00Z"
}
],
"meta": { "page": 1, "per_page": 50, "total": 5 }
}
}curl -H "Authorization: Bearer cdy_your_api_key" \ "https://conduyt.app/api/v1/forms"
Create a new lead capture form with custom fields and settings.
| Field | Description |
|---|---|
description | Description of the form's purpose |
fields | Array of field definitions. Each field: key, label, type (text, email, phone, textarea, select, number), required (boolean), options (for select type) |
hyrosTag | — |
isActive | — |
name | Form name (internal label) |
redirectUrl | URL the thank-you screen redirects to after a successful submission |
settings | Optional settings object. Submission processing reads settings.tags (tag NAMES applied to the created contact) and settings.consent; other keys are stored but not acted on. The post-submit redirect is the TOP-LEVEL redirectUrl field, not a settings key |
slug | — |
thankYouMessage | Message shown when no redirectUrl is set |
curl -X POST https://conduyt.app/api/v1/forms \
-H "Authorization: Bearer cdy_your_api_key" \
-H "Content-Type: application/json" \
-d '{
"name": "Free Consultation Request",
"description": "Landing page form for consultation signups",
"fields": [
{ "key": "name", "label": "Full Name", "type": "text", "required": true },
{ "key": "email", "label": "Email Address", "type": "email", "required": true },
{ "key": "phone", "label": "Phone", "type": "phone", "required": false },
{ "key": "service", "label": "Service Interested In", "type": "select", "required": true, "options": ["CRM Setup", "Data Migration", "Custom Integration"] }
],
"redirectUrl": "https://example.com/thank-you",
"settings": {
"tags": ["consultation-request"]
}
}'Retrieve a single form by ID, including its field definitions and submission count.
curl -H "Authorization: Bearer cdy_your_api_key" \ https://conduyt.app/api/v1/forms/a1b2c3d4-e5f6-4890-abcd-ef1234567890
Update a form. Only include fields you want to change.
curl -X PATCH https://conduyt.app/api/v1/forms/a1b2c3d4-e5f6-4890-abcd-ef1234567890 \
-H "Authorization: Bearer cdy_your_api_key" \
-H "Content-Type: application/json" \
-d '{ "name": "Updated Consultation Form", "settings": { "tags": ["hot-lead"] } }'Archives the form (sets it inactive). Existing submissions are retained — this is a deactivation, not an erasure; use the data-retention / GDPR workflow for actual deletion.
curl -X DELETE -H "Authorization: Bearer cdy_your_api_key" \ https://conduyt.app/api/v1/forms/a1b2c3d4-e5f6-4890-abcd-ef1234567890
{
"data": { "id": "a1b2c3d4-e5f6-4890-abcd-ef1234567890", "archived": true }
}List all submissions for a form, paginated. Each submission includes the submitted data and the auto-created contact reference.
| Parameter | Description |
|---|---|
page | Page number (default: 1) |
per_page | — |
{
"data": {
"data": [
{
"id": "9f8e7d6c-5b4a-4210-aedc-ba9876543210",
"formId": "a1b2c3d4-e5f6-4890-abcd-ef1234567890",
"data": {
"name": "Marcus Reeves",
"email": "marcus@reevesconsulting.com",
"phone": "+12125559900",
"service": "CRM Setup"
},
"contactId": "550e8400-e29b-41d4-a716-446655440099",
"submittedAt": "2026-04-18T14:22:00Z"
}
],
"meta": { "page": 1, "per_page": 50, "total": 247 }
}
}curl -H "Authorization: Bearer cdy_your_api_key" \ "https://conduyt.app/api/v1/forms/a1b2c3d4-e5f6-4890-abcd-ef1234567890/submissions?per_page=25"
Submit a form. This endpoint is public and requires no authentication. If the submitted data includes an email field, Conduyt automatically creates or matches an existing contact.
| Field | Description |
|---|---|
_hp_field | Honeypot — leave empty. A non-empty value silently discards the submission (bot protection). |
attribution | — |
consent | — |
data | Key-value pairs matching the form's field keys. Required fields must be present. |
source | — |
utm | — |
curl -X POST https://conduyt.app/api/v1/forms/a1b2c3d4-e5f6-4890-abcd-ef1234567890/submit \
-H "Content-Type: application/json" \
-d '{
"data": {
"name": "Elena Vasquez",
"email": "elena@luminadesigns.co",
"phone": "+13055557788",
"service": "Data Migration"
}
}'{
"data": {
"id": "1a2b3c4d-5e6f-4890-abcd-ef0987654321",
"contactId": "550e8400-e29b-41d4-a716-446655440100",
"redirectUrl": null,
"thankYouMessage": "Thanks! Your submission was received."
}
}Products
Manage a product catalog for use in invoices. Products define line items with pricing, SKUs, and tax settings.
List all products with optional search and active/inactive filtering.
| Parameter | Description |
|---|---|
is_active | — |
page | Page number (default: 1) |
per_page | — |
search | Search by product name or SKU |
{
"data": {
"data": [
{
"id": "7f8e9d0c-1b2a-4456-adef-789012345678",
"name": "CRM Setup & Configuration",
"description": "Full CRM workspace setup, pipeline configuration, and team onboarding",
"price": 2500.00,
"sku": "SVC-SETUP-001",
"unit": "project",
"taxable": true,
"active": true,
"createdAt": "2026-01-15T08:00:00Z",
"updatedAt": "2026-03-20T16:45:00Z"
},
{
"id": "6e5d4c3b-2a19-4987-aedc-ba6543210987",
"name": "Monthly CRM Subscription",
"description": "Standard tier monthly subscription with unlimited contacts",
"price": 499.00,
"sku": "SUB-STD-MO",
"unit": "month",
"taxable": true,
"active": true,
"createdAt": "2026-01-15T08:00:00Z",
"updatedAt": "2026-01-15T08:00:00Z"
}
],
"meta": { "page": 1, "per_page": 50, "total": 12 }
}
}curl -H "Authorization: Bearer cdy_your_api_key" \ "https://conduyt.app/api/v1/products?is_active=true&search=CRM"
Create a new product in the catalog.
| Field | Description |
|---|---|
currency | — |
description | Product description |
isActive | — |
metadata | — |
name | Product name |
price | Unit price in dollars |
sku | Stock keeping unit identifier |
taxable | Whether tax applies (default: true) |
unit | Unit of measure (e.g., hour, project, month, each) |
curl -X POST https://conduyt.app/api/v1/products \
-H "Authorization: Bearer cdy_your_api_key" \
-H "Content-Type: application/json" \
-d '{
"name": "Custom API Integration",
"description": "Build and deploy a custom integration with third-party systems",
"price": 3500.00,
"sku": "SVC-INT-001",
"unit": "project",
"taxable": true
}'Retrieve a single product by ID.
curl -H "Authorization: Bearer cdy_your_api_key" \ https://conduyt.app/api/v1/products/7f8e9d0c-1b2a-4456-adef-789012345678
Update a product. Only include fields you want to change.
curl -X PATCH https://conduyt.app/api/v1/products/7f8e9d0c-1b2a-4456-adef-789012345678 \
-H "Authorization: Bearer cdy_your_api_key" \
-H "Content-Type: application/json" \
-d '{ "price": 2750.00, "description": "Updated: includes 2 hours of training" }'Archives the product (sets it inactive) — the row is retained and it stops appearing in active product lists. Note: archival is a visibility flag; a client that still holds the product id can reference it on new invoices. Responds 200 with { "data": { "id": ..., "archived": true } }.
curl -X DELETE -H "Authorization: Bearer cdy_your_api_key" \ https://conduyt.app/api/v1/products/7f8e9d0c-1b2a-4456-adef-789012345678
Invoices
Create, send, and track invoices with line items. Invoices auto-calculate subtotals, tax, and totals. Record payments against invoices to track outstanding balances.
List all invoices with optional filters for status, contact, and date range.
| Parameter | Description |
|---|---|
contact_id | — |
date_from | — |
date_to | — |
page | Page number (default: 1) |
per_page | — |
search | — |
status | Filter by status: draft, sent, paid, overdue, void |
{
"data": {
"data": [
{
"id": "1c4d5e6f-7a8b-4c9d-8e0f-1a2b3c4d5e60",
"invoiceNumber": "INV-001",
"status": "sent",
"contact": { "id": "550e8400-e29b-41d4-a716-446655440000", "firstName": "Sarah", "lastName": "Kim" },
"subtotal": 3997.00,
"taxRate": 0.0875,
"taxAmount": 349.74,
"total": 4346.74,
"amountPaid": 0,
"dueDate": "2026-05-19",
"createdAt": "2026-04-19T10:00:00Z",
"updatedAt": "2026-04-19T10:30:00Z"
}
],
"meta": { "page": 1, "per_page": 50, "total": 28 }
}
}curl -H "Authorization: Bearer cdy_your_api_key" \ "https://conduyt.app/api/v1/invoices?status=sent&date_to=2026-05-01"
Create a new invoice with line items. Totals are auto-calculated from item quantities and unit prices.
| Field | Description |
|---|---|
companyId | — |
contactId | Contact to invoice. Optional — the handler persists null when omitted; only a non-empty items array is required. |
currency | — |
dealId | — |
dueDate | Payment due date. Default: 30 days from creation. |
invoiceNumber | — |
items | Line items. Each: description (string), quantity (number), unitPrice (number), optional productId (uuid) |
notes | Notes displayed on the invoice |
taxRate | Tax rate as decimal (e.g., 0.0875 for 8.75%). Default: workspace setting. |
terms | — |
curl -X POST https://conduyt.app/api/v1/invoices \
-H "Authorization: Bearer cdy_your_api_key" \
-H "Content-Type: application/json" \
-d '{
"contactId": "550e8400-e29b-41d4-a716-446655440000",
"items": [
{ "description": "CRM Setup & Configuration", "quantity": 1, "unitPrice": 2500.00, "productId": "7f8e9d0c-1b2a-4456-adef-789012345678" },
{ "description": "Monthly Subscription (3 months)", "quantity": 3, "unitPrice": 499.00 }
],
"taxRate": 0.0875,
"dueDate": "2026-05-19",
"notes": "Thank you for choosing Conduyt. Payment due within 30 days."
}'{
"data": {
"id": "1c4d5e6f-7a8b-4c9d-8e0f-1a2b3c4d5e60",
"invoiceNumber": "INV-001",
"status": "draft",
"contact": { "id": "550e8400-e29b-41d4-a716-446655440000", "firstName": "Sarah", "lastName": "Kim" },
"items": [
{ "id": "b2c3d4e5-e6f7-4819-8432-4c5d6e7f8091", "description": "CRM Setup & Configuration", "quantity": 1, "unitPrice": 2500.00, "amount": 2500.00 },
{ "id": "c3d4e5f6-f708-491a-9543-5d6e7f8091a2", "description": "Monthly Subscription (3 months)", "quantity": 3, "unitPrice": 499.00, "amount": 1497.00 }
],
"subtotal": 3997.00,
"taxRate": 0.0875,
"taxAmount": 349.74,
"total": 4346.74,
"amountPaid": 0,
"dueDate": "2026-05-19",
"notes": "Thank you for choosing Conduyt. Payment due within 30 days.",
"payments": [],
"createdAt": "2026-04-19T10:00:00Z",
"updatedAt": "2026-04-19T10:00:00Z"
}
}Retrieve a single invoice with all line items and payment history.
{
"data": {
"id": "1c4d5e6f-7a8b-4c9d-8e0f-1a2b3c4d5e60",
"invoiceNumber": "INV-001",
"status": "sent",
"contact": { "id": "550e8400-e29b-41d4-a716-446655440000", "firstName": "Sarah", "lastName": "Kim" },
"items": [
{ "id": "b2c3d4e5-e6f7-4819-8432-4c5d6e7f8091", "description": "CRM Setup & Configuration", "quantity": 1, "unitPrice": 2500.00, "amount": 2500.00 },
{ "id": "c3d4e5f6-f708-491a-9543-5d6e7f8091a2", "description": "Monthly Subscription (3 months)", "quantity": 3, "unitPrice": 499.00, "amount": 1497.00 }
],
"subtotal": 3997.00,
"taxRate": 0.0875,
"taxAmount": 349.74,
"total": 4346.74,
"amountPaid": 0,
"dueDate": "2026-05-19",
"notes": "Thank you for choosing Conduyt. Payment due within 30 days.",
"payments": [],
"createdAt": "2026-04-19T10:00:00Z",
"updatedAt": "2026-04-19T10:30:00Z"
}
}curl -H "Authorization: Bearer cdy_your_api_key" \ https://conduyt.app/api/v1/invoices/1c4d5e6f-7a8b-4c9d-8e0f-1a2b3c4d5e60
Update a draft invoice. Sent and paid invoices cannot be edited. Void and recreate instead.
curl -X PATCH https://conduyt.app/api/v1/invoices/1c4d5e6f-7a8b-4c9d-8e0f-1a2b3c4d5e60 \
-H "Authorization: Bearer cdy_your_api_key" \
-H "Content-Type: application/json" \
-d '{ "dueDate": "2026-06-01", "notes": "Extended payment terms — NET 45" }'Mark an invoice as sent. Changes status from "draft" to "sent" and records the sent timestamp.
curl -X POST -H "Authorization: Bearer cdy_your_api_key" \ https://conduyt.app/api/v1/invoices/1c4d5e6f-7a8b-4c9d-8e0f-1a2b3c4d5e60/send
{
"data": {
"id": "1c4d5e6f-7a8b-4c9d-8e0f-1a2b3c4d5e60",
"invoiceNumber": "INV-001",
"status": "sent",
"sentAt": "2026-04-19T10:30:00Z"
}
}Void an invoice. Voided invoices cannot be edited or paid. This action cannot be undone.
curl -X POST -H "Authorization: Bearer cdy_your_api_key" \ https://conduyt.app/api/v1/invoices/1c4d5e6f-7a8b-4c9d-8e0f-1a2b3c4d5e60/void
List all payments recorded against an invoice.
{
"data": [
{
"id": "d4c3b2a1-0987-4543-aedc-ba0987654321",
"invoiceId": "550e8400-e29b-41d4-a716-446655440000",
"amount": 2500.00,
"method": "bank_transfer",
"reference": "Wire ref #4821",
"notes": "Partial payment — setup fee",
"recordedAt": "2026-04-25T14:00:00Z",
"recordedBy": "3f8b1c22-9d4e-4a17-b0c5-6e2f7a8d9b31"
}
]
}curl -H "Authorization: Bearer cdy_your_api_key" \ https://conduyt.app/api/v1/invoices/1c4d5e6f-7a8b-4c9d-8e0f-1a2b3c4d5e60/payments
Record a payment against an invoice. Automatically updates the invoice's amountPaid and transitions status to "paid" when fully paid.
| Field | Description |
|---|---|
amount | Payment amount in dollars |
method | Payment method: credit_card, bank_transfer, check, cash, other |
notes | Internal notes about this payment |
paidAt | — |
reference | External reference (check number, wire ref, etc.) |
curl -X POST https://conduyt.app/api/v1/invoices/1c4d5e6f-7a8b-4c9d-8e0f-1a2b3c4d5e60/payments \
-H "Authorization: Bearer cdy_your_api_key" \
-H "Content-Type: application/json" \
-d '{
"amount": 1846.74,
"method": "credit_card",
"reference": "ch_3Oa1b2c3d4e5f6",
"notes": "Final payment — balance cleared"
}'{
"data": {
"id": "e5d4c3b2-a109-4765-a321-fedcba098765",
"invoiceId": "550e8400-e29b-41d4-a716-446655440000",
"amount": 1846.74,
"method": "credit_card",
"reference": "ch_3Oa1b2c3d4e5f6",
"notes": "Final payment — balance cleared",
"recordedAt": "2026-04-28T09:15:00Z",
"recordedBy": "3f8b1c22-9d4e-4a17-b0c5-6e2f7a8d9b31"
},
"invoice": {
"id": "550e8400-e29b-41d4-a716-446655440000",
"status": "paid",
"total": 4346.74,
"amountPaid": 4346.74
}
}Email Templates
Create and manage reusable email templates with merge fields for personalized outreach. Templates support HTML content and dynamic placeholders like {{contact.firstName}} and {{contact.company}}.
List all email templates in the workspace.
| Parameter | Description |
|---|---|
category | — |
channel | — |
is_active | — |
page | Page number (default: 1) |
per_page | — |
provider | — |
{
"data": [
{
"id": "a1b2c3d4-5678-4012-abcd-ef3456789012",
"name": "Welcome — New Client Onboarding",
"subject": "Welcome to {{workspace.name}}, {{contact.firstName}}!",
"bodyHtml": "<h1>Welcome aboard, {{contact.firstName}}!</h1><p>We're excited to have {{contact.company}} as a client...</p>",
"bodyText": "Welcome aboard, {{contact.firstName}}! We're excited to have {{contact.company}} as a client...",
"mergeFields": ["contact.firstName", "contact.company", "workspace.name"],
"createdAt": "2026-03-01T12:00:00Z",
"updatedAt": "2026-04-10T09:30:00Z"
}
],
"meta": { "page": 1, "perPage": 50, "total": 8, "totalPages": 1 }
}curl -H "Authorization: Bearer cdy_your_api_key" \ "https://conduyt.app/api/v1/emails/templates?channel=email"
Create a new email template with HTML content and merge fields.
| Field | Description |
|---|---|
body | Required. Plain-text body (supports merge fields) |
bodyHtml | HTML email body (supports merge fields) |
category | — |
channel | Required. sms or email |
isActive | — |
name | Required. Internal template name |
provider | — |
subject | Email subject line (supports merge fields) |
curl -X POST https://conduyt.app/api/v1/emails/templates \
-H "Authorization: Bearer cdy_your_api_key" \
-H "Content-Type: application/json" \
-d '{
"name": "Follow-Up — Meeting Recap",
"channel": "email",
"subject": "Great meeting, {{contact.firstName}} — next steps",
"body": "Hi {{contact.firstName}}, thanks for meeting with us today. Here are the next steps we discussed...",
"bodyHtml": "<p>Hi {{contact.firstName}},</p><p>Thanks for meeting with us today. Here are the next steps we discussed...</p>"
}'Retrieve a single email template by ID.
curl -H "Authorization: Bearer cdy_your_api_key" \ https://conduyt.app/api/v1/emails/templates/a1b2c3d4-5678-4012-abcd-ef3456789012
Update an email template. Only include fields you want to change.
curl -X PATCH https://conduyt.app/api/v1/emails/templates/a1b2c3d4-5678-4012-abcd-ef3456789012 \
-H "Authorization: Bearer cdy_your_api_key" \
-H "Content-Type: application/json" \
-d '{ "subject": "Next steps for {{contact.company}}" }'Archives the template (sets it inactive; historical messages keep a stable template identity). A template referenced by an active sequence or workflow answers 409 listing the uses — archive or replace those references first. Success responds 200 with { "data": { "id": ..., "deleted": true, "archived": true } }.
curl -X DELETE -H "Authorization: Bearer cdy_your_api_key" \ https://conduyt.app/api/v1/emails/templates/a1b2c3d4-5678-4012-abcd-ef3456789012
Email Sequences
Automated multi-step email sequences. Define a series of timed emails, enroll contacts, and track engagement across the drip campaign.
List all email sequences with enrollment stats.
{
"data": {
"data": [
{
"id": "b2c3d4e5-f6a7-4901-bcde-f23456789012",
"name": "New Client Onboarding Drip",
"status": "active",
"steps": [
{ "order": 1, "templateId": "a1b2c3d4-5678-4012-abcd-ef3456789012", "delayDays": 0, "subject": "Welcome to Conduyt!" },
{ "order": 2, "templateId": "e5f6a7b8-9012-4345-8def-012345678901", "delayDays": 3, "subject": "Getting started with pipelines" },
{ "order": 3, "templateId": "c9d0e1f2-3456-4789-9abc-de0123456789", "delayDays": 7, "subject": "Pro tips from our team" }
],
"enrolledCount": 142,
"completedCount": 98,
"activeCount": 44,
"createdAt": "2026-02-15T10:00:00Z",
"updatedAt": "2026-04-18T08:00:00Z"
}
],
"meta": { "page": 1, "per_page": 50, "total": 4 }
}
}curl -H "Authorization: Bearer cdy_your_api_key" \ "https://conduyt.app/api/v1/emails/sequences"
Create a new email sequence with ordered steps. Each step references a template and a delay (in days) from enrollment.
| Field | Description |
|---|---|
autoUnenrollOnBounce | — |
autoUnenrollOnMeeting | — |
autoUnenrollOnReply | — |
autoUnenrollOnUnsubscribe | — |
description | — |
isActive | — |
name | Sequence name |
steps | Ordered steps. Each: templateId (uuid), delayDays (integer, days after enrollment), optional subject override |
curl -X POST https://conduyt.app/api/v1/emails/sequences \
-H "Authorization: Bearer cdy_your_api_key" \
-H "Content-Type: application/json" \
-d '{
"name": "Trial Nurture Sequence",
"steps": [
{ "templateId": "a1b2c3d4-5678-4012-abcd-ef3456789012", "delayDays": 0, "subject": "Welcome to your trial!" },
{ "templateId": "e5f6a7b8-9012-4345-8def-012345678901", "delayDays": 2, "subject": "Did you set up your first pipeline?" },
{ "templateId": "c9d0e1f2-3456-4789-9abc-de0123456789", "delayDays": 5 },
{ "templateId": "01a2b3c4-5678-4901-9234-567890123456", "delayDays": 12, "subject": "Your trial ends in 2 days" }
]
}'Retrieve a single sequence with its steps and enrollment statistics.
curl -H "Authorization: Bearer cdy_your_api_key" \ https://conduyt.app/api/v1/emails/sequences/b2c3d4e5-f6a7-4901-bcde-f23456789012
Update a sequence. Modifying steps on an active sequence only affects future enrollments. Contacts already in progress continue on the original steps.
curl -X PATCH https://conduyt.app/api/v1/emails/sequences/b2c3d4e5-f6a7-4901-bcde-f23456789012 \
-H "Authorization: Bearer cdy_your_api_key" \
-H "Content-Type: application/json" \
-d '{ "name": "Updated Onboarding Drip" }'Enroll one or more contacts into a sequence. Contacts already enrolled are silently skipped.
| Field | Description |
|---|---|
contactIds | Array of contact IDs to enroll (max 100 per request) |
curl -X POST https://conduyt.app/api/v1/emails/sequences/b2c3d4e5-f6a7-4901-bcde-f23456789012/enroll \
-H "Authorization: Bearer cdy_your_api_key" \
-H "Content-Type: application/json" \
-d '{ "contactIds": ["550e8400-e29b-41d4-a716-446655440000", "6b1f2e93-4c7a-4d18-9e05-3a7b8c9d0e12", "7d2a3f84-5b6c-4e29-8f10-4b8c9d0e1f23"] }'{
"enrolled": 2,
"skipped": 1,
"details": [
{ "contactId": "550e8400-e29b-41d4-a716-446655440000", "status": "enrolled" },
{ "contactId": "6b1f2e93-4c7a-4d18-9e05-3a7b8c9d0e12", "status": "enrolled" },
{ "contactId": "7d2a3f84-5b6c-4e29-8f10-4b8c9d0e1f23", "status": "skipped", "reason": "already_enrolled" }
]
}Remove a contact from a sequence. Stops all future emails in the sequence for this contact.
| Field | Description |
|---|---|
contactId | Contact ID to unenroll |
curl -X POST https://conduyt.app/api/v1/emails/sequences/b2c3d4e5-f6a7-4901-bcde-f23456789012/unenroll \
-H "Authorization: Bearer cdy_your_api_key" \
-H "Content-Type: application/json" \
-d '{ "contactId": "550e8400-e29b-41d4-a716-446655440000" }'List all enrollments for a sequence, including current step and completion status.
| Parameter | Description |
|---|---|
page | Page number (default: 1) |
per_page | — |
status | Filter by enrollment status: active, paused, completed, cancelled, unsubscribed, failed |
{
"data": {
"data": [
{
"id": "c3d4e5f6-a7b8-4012-adef-345678901234",
"contactId": "2d5e6f7a-8b9c-4d0e-9f10-2b3c4d5e6f71",
"contact": { "firstName": "Marcus", "lastName": "Reeves", "email": "marcus@reevesconsulting.com" },
"status": "active",
"currentStep": 2,
"totalSteps": 3,
"enrolledAt": "2026-04-15T10:00:00Z",
"lastEmailSentAt": "2026-04-18T10:00:00Z",
"nextEmailAt": "2026-04-22T10:00:00Z"
}
],
"meta": { "page": 1, "per_page": 50, "total": 44 }
}
}curl -H "Authorization: Bearer cdy_your_api_key" \ "https://conduyt.app/api/v1/emails/sequences/b2c3d4e5-f6a7-4901-bcde-f23456789012/enrollments?status=active"
Calls
Log and track phone calls with contacts. Calls appear in the contact's activity timeline and can be filtered by direction, status, user, and date range.
List all logged calls with optional filters.
| Parameter | Description |
|---|---|
contact_id | — |
date_from | — |
date_to | — |
direction | Filter by direction: inbound, outbound |
minimal | — |
page | Page number (default: 1) |
per_page | — |
status | Filter by status: completed, missed, voicemail, no_answer |
user_id | — |
{
"data": {
"data": [
{
"id": "d4e5f6a7-b8c9-4123-8ef4-567890123456",
"contactId": "550e8400-e29b-41d4-a716-446655440000",
"contact": { "firstName": "Sarah", "lastName": "Kim" },
"userId": "3f8b1c22-9d4e-4a17-b0c5-6e2f7a8d9b31",
"user": { "name": "David Park" },
"direction": "outbound",
"status": "completed",
"duration": 342,
"notes": "Discussed CRM migration timeline. Sarah confirmed Q2 start. Follow up with SOW by Friday.",
"startedAt": "2026-04-18T15:30:00Z",
"endedAt": "2026-04-18T15:35:42Z",
"createdAt": "2026-04-18T15:35:42Z"
}
],
"meta": { "page": 1, "per_page": 50, "total": 89 }
}
}curl -H "Authorization: Bearer cdy_your_api_key" \ "https://conduyt.app/api/v1/calls?direction=outbound&status=completed&date_from=2026-04-01"
Log a new call. Creates an entry in the contact's activity timeline.
| Field | Description |
|---|---|
contactId | Contact the call is with |
direction | Call direction: inbound or outbound |
disposition | — |
duration | Call duration in seconds |
fromNumber | Required unless source is "manual_log" |
notes | Call notes or summary |
source | Send "manual_log" when logging a call by hand. WITHOUT it the call is treated as a telephony record and fromNumber + toNumber become required. |
status | Call outcome: completed, missed, voicemail, no_answer |
toNumber | Required unless source is "manual_log" |
curl -X POST https://conduyt.app/api/v1/calls \
-H "Authorization: Bearer cdy_your_api_key" \
-H "Content-Type: application/json" \
-d '{
"contactId": "550e8400-e29b-41d4-a716-446655440000",
"direction": "outbound",
"source": "manual_log",
"status": "completed",
"duration": 480,
"notes": "Demo call with Northwind Clinical. Showed pipeline + automation features. They want a proposal by next week."
}'Retrieve a single call log entry by ID.
curl -H "Authorization: Bearer cdy_your_api_key" \ https://conduyt.app/api/v1/calls/d4e5f6a7-b8c9-4123-8ef4-567890123456
Update a call log entry. Commonly used to add notes after a call or correct the status.
curl -X PATCH https://conduyt.app/api/v1/calls/d4e5f6a7-b8c9-4123-8ef4-567890123456 \
-H "Authorization: Bearer cdy_your_api_key" \
-H "Content-Type: application/json" \
-d '{ "notes": "Updated: Sarah confirmed budget approval. Moving to negotiation stage.", "status": "completed" }'Call Flows
Inbound call routing. A call flow answers one of your Twilio voice numbers and walks a graph of steps: an optional greeting, ring one person or a team (everyone at once, one at a time, or weighted) anywhere in the flow, hold the caller (optionally in queue mode — the team keeps ringing while they hold), a menu (the caller presses a key; each key is its own branch), a schedule (business hours in the workspace timezone; during-hours and outside-hours branches), then an ending (voicemail, forward to an outside number, or hang up). Every path must end: publish refuses a branch that leads nowhere or loops. Edit the draftGraph, validate, then publish — live calls keep the version they started on. API key scope: settings.
{
"entryId": "0c3a2e6e-4d1f-4b1a-9f3e-6a2b7c8d9e01",
"nodes": [
{ "id": "0c3a2e6e-4d1f-4b1a-9f3e-6a2b7c8d9e01", "kind": "entry", "next": "1d4b3f7f-5e2a-4c2b-8a4f-7b3c8d9e0f12" },
{ "id": "1d4b3f7f-5e2a-4c2b-8a4f-7b3c8d9e0f12", "kind": "greeting", "text": "Thanks for calling.", "next": "4a7e6b0c-8d5e-4f6a-9c1b-0e2f3a4b5c67" },
{ "id": "4a7e6b0c-8d5e-4f6a-9c1b-0e2f3a4b5c67", "kind": "timing", "hours": [{ "day": 1, "start": "09:00", "end": "17:00" }, { "day": 2, "start": "09:00", "end": "17:00" }],
"timezone": null, "next": "5b8f7c1d-9e6f-4a7b-8d2c-1f3a4b5c6d78", "closedNext": "6c9a8d2e-0f7a-4b8c-9e3d-2a4b5c6d7e89" },
{ "id": "5b8f7c1d-9e6f-4a7b-8d2c-1f3a4b5c6d78", "kind": "menu", "text": "Press 1 for sales, 2 for support.",
"options": [{ "digit": "1", "label": "Sales", "next": "2e5c4a8a-6f3b-4d3c-9b5a-8c4d9e0f1a23" }, { "digit": "2", "label": "Support", "next": "3f6d5b9b-7a4c-4e4d-8c6b-9d5e0f1a2b34" }],
"timeoutSeconds": 6, "retries": 2, "next": "6c9a8d2e-0f7a-4b8c-9e3d-2a4b5c6d7e89" },
{ "id": "2e5c4a8a-6f3b-4d3c-9b5a-8c4d9e0f1a23", "kind": "group", "name": "Front Line", "mode": "round_robin",
"members": [{ "userId": "3f8b1c22-9d4e-4a17-b0c5-6e2f7a8d9b31" }, { "userId": "7c1d2e3f-4a5b-4c6d-8e7f-9a0b1c2d3e4f" }], "ringSeconds": 20,
"queue": { "holdSeconds": 120, "pressOneVoicemail": true, "repeatRing": true }, "next": "6c9a8d2e-0f7a-4b8c-9e3d-2a4b5c6d7e89" },
{ "id": "3f6d5b9b-7a4c-4e4d-8c6b-9d5e0f1a2b34", "kind": "forward", "phoneNumber": "+18005550177", "next": null },
{ "id": "6c9a8d2e-0f7a-4b8c-9e3d-2a4b5c6d7e89", "kind": "voicemail", "text": "Leave a message.", "next": null }
]
}
// kinds: entry | greeting{text?,audioUrl?} | agent{userId,ringSeconds} | group
// | menu{text?,audioUrl?,options[{digit,label?,next}],timeoutSeconds?,retries?} next = no-choice path
// | timing{hours[{day 0-6,start "HH:MM",end "HH:MM"}],timezone?,closedNext} next = during hours
// | voicemail{text?} | forward{phoneNumber} | hangup{message?}
// group.mode: simultaneous | round_robin | weighted (member.weight, must total 100)
// group.queue: { holdSeconds 10-300, pressOneVoicemail?, repeatRing? } repeatRing = queue mode (re-ring while holding)
// Every branch (next, closedNext, options[].next) must reach an ending — validate reports path_no_ending otherwise.
// POST (supplied) / PATCH draftGraph → 422 when a node id is blank or duplicated, or a menu's options is not an array of { digit } objects (#50: create never substitutes the seed for a malformed graph).
// Publish blockers (code): bad_graph | unreachable_node | path_no_ending | missing_ending | bad_menu | bad_timing
// | bad_ring_seconds | weights_not_100 | bad_forward_number | empty_group | member_no_phone | number_claimed | number_missingList call flows with their draft and published graphs, draftRevision, and today's call count.
Create a draft flow. name required; phoneNumber optional (E.164, must be free); draftGraph optional non-null graph object — omit it and the draft is seeded with entry → voicemail; send one and it is validated exactly like PATCH (null or any malformed graph is a 422, never silently replaced). Admin.
| Field | Description |
|---|---|
draftGraph | Optional { entryId, nodes[] }. Omitted → seeded entry → voicemail. Supplied → validated like PATCH (blank/duplicate ids, unrenderable node shapes, or null → 422) |
name | Display name |
phoneNumber | E.164 number this flow will answer (optional; claim-checked) |
The full flow row, including draftRevision and updatedAt (both needed for writes).
Update name, phoneNumber, or draftGraph. A draftGraph write must send expectedDraftRevision (the revision you last read): a concurrent edit returns 409, a missing token 428. A published flow's number is locked until you unpublish.
curl -X PATCH https://conduyt.app/api/v1/call-flows/a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d \
-H "Authorization: Bearer cdy_your_key" -H "Content-Type: application/json" \
-d '{ "name": "Front line", "expectedDraftRevision": 7, "draftGraph": { "entryId": "0c3a2e6e-4d1f-4b1a-9f3e-6a2b7c8d9e01", "nodes": [ { "id": "0c3a2e6e-4d1f-4b1a-9f3e-6a2b7c8d9e01", "kind": "entry", "next": "3f6d5b9b-7a4c-4e4d-8c6b-9d5e0f1a2b34" }, { "id": "3f6d5b9b-7a4c-4e4d-8c6b-9d5e0f1a2b34", "kind": "voicemail", "next": null } ] } }'Publish blockers for the current draft as { "blockers": [{ "nodeId", "code", "message" }] }. Codes: number_missing, number_claimed, member_no_phone, empty_group, weights_not_100, bad_forward_number, bad_ring_seconds, unreachable_node, bad_graph.
Promote the draft to live. Requires expectedUpdatedAt (the flow's updatedAt from your last read/save). Verifies the number is owned by your Twilio, blocks forward loops and number conflicts, snapshots the version, then points the number's Twilio voice webhook at Conduyt: the response carries wiring (wired, inboundUrl, voiceUrl, error).
Take the flow off its number. Calls in progress finish on their pinned version; new calls fall through to standard routing.
Walk the draft against live presence: who would ring, in what order, and where the call ends if nobody answers.
Active team members with their phone (did) and presence (on, call, off). Members without a phone cannot be placed in a flow.
The voice numbers your Twilio account owns, each with what answers it in Conduyt today (claimedBy: call flow, ring group, or main line) and whether its voice webhook is already wired to Conduyt.
What Twilio has on the flow's number right now versus the inboundUrl Conduyt needs.
Point the number's Twilio voice webhook at Conduyt (the same step publish performs). Admin. Returns the resulting wiring state; 502 when Twilio refused.
Release a number still held by a deleted call flow's tombstone. When a flow is renumbered or deleted while something else in Conduyt still answers its number, the flow keeps a tenant-resolution tombstone on that number; this puts the number back to its pre-Conduyt Twilio configuration (verified) and lets the tombstone go. Admin. Body: { phoneNumber }. 404 when no tombstone holds the number; 409 when the number still sends calls to Conduyt and no prior configuration is known (fix it in the Twilio console first); 502 when the restore could not be verified.
Workflows
Advanced workflow automation with event-driven triggers and configurable actions. Define workflows that fire on CRM events (contact created, deal stage changed, form submitted) and execute actions like sending emails, creating tasks, or updating fields. Monitor execution with the runs API.
List all workflows in the workspace.
| Parameter | Description |
|---|---|
is_active | — |
page | Page number (default: 1) |
per_page | — |
{
"data": {
"data": [
{
"id": "e5f6a7b8-c9d0-4234-8f56-789012345678",
"name": "New Lead Auto-Assignment",
"description": "Assigns new leads from the website form to the sales team round-robin",
"status": "active",
"trigger": {
"event": "contact.created",
"conditions": { "source": "website" }
},
"actions": [
{ "type": "assign_contact", "config": { "strategy": "round_robin", "teamId": "d4e5f607-0819-4a2b-8654-6e7f8091a2b3" } },
{ "type": "send_email", "config": { "templateId": "a1b2c3d4-5678-4012-abcd-ef3456789012", "delay": 0 } },
{ "type": "create_task", "config": { "title": "Follow up with new lead", "dueDays": 1 } }
],
"runCount": 312,
"lastRunAt": "2026-04-19T08:15:00Z",
"createdAt": "2026-02-01T10:00:00Z",
"updatedAt": "2026-04-10T14:00:00Z"
}
],
"meta": { "page": 1, "per_page": 50, "total": 6 }
}
}curl -H "Authorization: Bearer cdy_your_api_key" \ "https://conduyt.app/api/v1/workflows?is_active=true"
Create a new workflow with a trigger event and one or more actions. Workflows are created in "inactive" status by default. Activate explicitly when ready.
| Field | Description |
|---|---|
actions | Ordered actions. Each: type (assign_contact, send_email, create_task, update_field, send_webhook, enroll_sequence), config (object) |
description | Description of what this workflow does |
isActive | — |
name | Workflow name |
trigger | Trigger definition: event (string — e.g., contact.created, deal.stage_changed, form.submitted), optional conditions (object) |
curl -X POST https://conduyt.app/api/v1/workflows \
-H "Authorization: Bearer cdy_your_api_key" \
-H "Content-Type: application/json" \
-d '{
"name": "Deal Won — Send Thank You",
"description": "Sends a thank-you email and creates a follow-up task when a deal is won",
"trigger": {
"event": "deal.stage_changed",
"conditions": { "stageName": "Closed Won" }
},
"actions": [
{ "type": "send_email", "config": { "templateId": "2b3c4d5e-6789-4012-8345-678901234567", "delay": 0 } },
{ "type": "create_task", "config": { "title": "Schedule kickoff call", "dueDays": 2, "assignTo": "deal.owner" } }
]
}'Retrieve a single workflow with its trigger, actions, and run statistics.
curl -H "Authorization: Bearer cdy_your_api_key" \ https://conduyt.app/api/v1/workflows/e5f6a7b8-c9d0-4234-8f56-789012345678
Update a workflow. Active workflows can be updated. Changes take effect on the next trigger event.
curl -X PATCH https://conduyt.app/api/v1/workflows/e5f6a7b8-c9d0-4234-8f56-789012345678 \
-H "Authorization: Bearer cdy_your_api_key" \
-H "Content-Type: application/json" \
-d '{ "name": "Deal Won — Full Onboarding Flow", "description": "Updated to include sequence enrollment" }'Delete a workflow. Deletion is unconditional — an active workflow is deleted too, so deactivate first if you want a grace period. Responds 200 with { "data": { "id": ..., "deleted": true } }.
curl -X DELETE -H "Authorization: Bearer cdy_your_api_key" \ https://conduyt.app/api/v1/workflows/e5f6a7b8-c9d0-4234-8f56-789012345678
Activate a workflow. Once active, the workflow will fire on matching trigger events.
curl -X POST -H "Authorization: Bearer cdy_your_api_key" \ https://conduyt.app/api/v1/workflows/e5f6a7b8-c9d0-4234-8f56-789012345678/activate
{
"data": {
"id": "e5f6a7b8-c9d0-4234-8f56-789012345678",
"name": "Deal Won — Full Onboarding Flow",
"status": "active",
"activatedAt": "2026-04-19T12:00:00Z"
}
}Deactivate a workflow. Stops it from firing on new events. In-flight runs continue to completion.
curl -X POST -H "Authorization: Bearer cdy_your_api_key" \ https://conduyt.app/api/v1/workflows/e5f6a7b8-c9d0-4234-8f56-789012345678/deactivate
List execution history for a workflow. Each run represents one trigger event and the resulting action executions.
| Parameter | Description |
|---|---|
page | Page number (default: 1) |
per_page | — |
status | Filter by run status: success, failed, running |
{
"data": {
"data": [
{
"id": "f6a7b8c9-d0e1-4345-a789-0abcdef01234",
"workflowId": "e5f6a7b8-c9d0-4234-8f56-789012345678",
"status": "success",
"triggerEvent": "deal.stage_changed",
"triggerData": { "dealId": "4f7a8b9c-0d1e-4f2a-9b32-4d5e6f7a8b93", "dealName": "Ritual & Glass — CRM Migration", "newStage": "Closed Won" },
"actions": [
{ "type": "send_email", "status": "success", "executedAt": "2026-04-19T08:15:01Z" },
{ "type": "create_task", "status": "success", "executedAt": "2026-04-19T08:15:02Z" }
],
"startedAt": "2026-04-19T08:15:00Z",
"completedAt": "2026-04-19T08:15:02Z",
"durationMs": 2100
}
],
"meta": { "page": 1, "per_page": 50, "total": 312 }
}
}curl -H "Authorization: Bearer cdy_your_api_key" \ "https://conduyt.app/api/v1/workflows/e5f6a7b8-c9d0-4234-8f56-789012345678/runs?status=failed&per_page=10"
Get full details of a specific workflow run, including per-action results and any error messages.
{
"data": {
"id": "f6a7b8c9-d0e1-4345-a789-0abcdef01234",
"workflowId": "e5f6a7b8-c9d0-4234-8f56-789012345678",
"workflowName": "Deal Won — Full Onboarding Flow",
"status": "success",
"triggerEvent": "deal.stage_changed",
"triggerData": {
"dealId": "4f7a8b9c-0d1e-4f2a-9b32-4d5e6f7a8b93",
"dealName": "Ritual & Glass — CRM Migration",
"contactId": "550e8400-e29b-41d4-a716-446655440000",
"previousStage": "Proposal",
"newStage": "Closed Won",
"dealValue": 18500
},
"actions": [
{
"type": "send_email",
"status": "success",
"config": { "templateId": "2b3c4d5e-6789-4012-8345-678901234567" },
"result": { "messageId": "1e2f3a4b-5c6d-4e7f-8a98-a2b3c4d5e6f7", "recipient": "sarah.kim@acme.com" },
"executedAt": "2026-04-19T08:15:01Z"
},
{
"type": "create_task",
"status": "success",
"config": { "title": "Schedule kickoff call", "dueDays": 2 },
"result": { "taskId": "5e6f7a8b-9c0d-4e1f-8a32-4c5d6e7f8091" },
"executedAt": "2026-04-19T08:15:02Z"
}
],
"startedAt": "2026-04-19T08:15:00Z",
"completedAt": "2026-04-19T08:15:02Z",
"durationMs": 2100
}
}curl -H "Authorization: Bearer cdy_your_api_key" \ https://conduyt.app/api/v1/workflows/e5f6a7b8-c9d0-4234-8f56-789012345678/runs/f6a7b8c9-d0e1-4345-a789-0abcdef01234
Need help integrating?
Check out the developer guides for step-by-step walkthroughs, or reach out to our team.