NEWConduyt v3 is live: native email + SMS campaigns, now with flow-based automation.See what's new →
API Reference · v1

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.

REST API · v1

API Reference

The same endpoints that power the Conduyt web app. Every request requires a Bearer token. All responses return JSON.

Base URL: https://conduyt.app/api/v1

Authentication

All requests require a Bearer token in the Authorization header. Generate API keys in Settings → API. Keys are prefixed with cdy_.

Authorization Header
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 Response Format
{
  "error": "pipelineId: pipelineId must be a UUID — resolve a pipeline NAME first via POST /api/v1/automations/resolve"
}
With optional fields
{
  "error": "Rate limit exceeded",
  "code": "rate_limited",
  "retryAfter": 42
}
StatusDescription
400Malformed or invalid request body / query — the message names the failing field
401Missing or invalid API key
402Billing inactive for the workspace
403Key lacks the required scope or role
404Resource does not exist in your workspace
413Payload too large (e.g. customFields over 64 KB)
422Semantic validation error (some endpoints)
429Rate limit exceeded — honor retryAfter
500Server error — safe to retry idempotent requests

Contacts

Manage contacts in your CRM. Contacts represent people your team interacts with: leads, customers, partners.

GET/api/v1/contacts

List all contacts with optional filters and pagination.

200 OK401 Unauthorized
Query Parameters
ParameterDescription
cf
cf[<key>]
assigned_toFilter 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
pagePage number (default: 1)
per_page
reachability
searchSearch by name, email, or phone
smartListId
smart_view
sort
sourceFilter by lead source
tagFilter by tag name
tags
updated_from
updated_to
Response
200 OK
{
  "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 }
  }
}
Example
curl
curl -H "Authorization: Bearer cdy_your_api_key" \
  "https://conduyt.app/api/v1/contacts?search=sarah&per_page=10"
POST/api/v1/contacts

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.

201 Created400 Validation
Request Body
FieldDescription
firstNamestringoptionalContact first name. max 100 chars. nullable
lastNamestringoptionalContact last name. max 100 chars. nullable
emailstringoptionalEmail address. max 254 chars. nullable
phonestringoptionalPhone number (E.164 format). max 30 chars. nullable
companystringoptionalCompany name. max 200 chars. nullable
companyIdstringoptionalmax 100 chars. nullable
jobTitlestringoptionalmax 150 chars. nullable
sourcestringoptionalLead source (e.g., website, referral, ad). max 100 chars. nullable
addressLine1stringoptionalmax 200 chars. nullable
addressLine2stringoptionalmax 200 chars. nullable
citystringoptionalmax 100 chars. nullable
statestringoptionalmax 100 chars. nullable
zipstringoptionalmax 20 chars. nullable
countrystringoptionalmax 2 chars. nullable
timezonestringoptionalmax 64 chars. nullable
legacyStatusstringoptionalmax 200 chars. nullable
masterStatusstringoptionalmax 40 chars
externalBackendbooleanoptionalnullable
languagestringoptionalmax 16 chars. nullable
doNotContactbooleanoptionalnullable
doNotContactReasonstringoptionalmax 500 chars. nullable
assignedTostringoptionalUser ID to assign this contact to. max 100 chars. nullable
tagsstring[]optionalArray of tag names to apply. max 100 items. each max 100 chars
customFieldsobjectoptionalKey-value pairs for custom fields. has conditional cross-field requirements
attributionobjectoptionalKeys: utm_source (string), utm_medium (string), utm_campaign (string), utm_content (string), utm_term (string), referrer (string), landingUrl (string), pageTitle (string), clickIds (object)
Example
curl
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"]
  }'
GET/api/v1/contacts/:id

Retrieve a single contact by ID. Returns the full contact object including custom fields and tags.

200 OK404 Not Found
curl
curl -H "Authorization: Bearer cdy_your_api_key" \
  https://conduyt.app/api/v1/contacts/550e8400-e29b-41d4-a716-446655440000
PATCH/api/v1/contacts/:id

Update a contact. Only include fields you want to change. Unspecified fields are left unchanged.

200 OK404 Not Found400 Validation
curl
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" }'
DELETE/api/v1/contacts/:id

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.

200 OK404 Not Found
curl
curl -X DELETE -H "Authorization: Bearer cdy_your_api_key" \
  https://conduyt.app/api/v1/contacts/550e8400-e29b-41d4-a716-446655440000
POST/api/v1/contacts/:id/tags

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.

201 Created400 Validation
curl
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"] }'
DELETE/api/v1/contacts/:id/tags/:tagId

Remove a specific tag from a contact.

200 OK
curl
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
POST/api/v1/contacts/import

Retired legacy import endpoint. It returns 410 Gone. Use the canonical import job APIs instead.

410 Gone
Canonical Replacement
EndpointDescription
POST /api/v1/importsCreate an import job and upload/import source metadata.
POST /api/v1/imports/:id/processRun canonical validation, duplicate handling, re-enrollment controls, and side effects for the job.
Response · 410 Gone
{
  "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.

GET/api/v1/companies

List all companies with optional search and pagination.

200 OK
ParameterDescription
hasOpenDeals
industry
lifecycleStage
order
ownerId
pagePage number (default: 1)
per_page
searchSearch by company name
size
sort
200 OK
{
  "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 }
  }
}
POST/api/v1/companies

Create a new company.

201 Created422 Validation
FieldDescription
namestringrequiredCompany name. max 200 chars
domainstringoptionalWebsite domain. max 200 chars. nullable
industrystringoptionalIndustry classification. max 100 chars. nullable
sizeenumoptionalCompany size bracket. One of: 1-10, 11-50, 51-200, 201-500, 501-1000, 1001+. nullable
annualRevenuenumberoptionalnullable
ownerIduuidoptionalnullable
parentCompanyIduuidoptionalnullable
lifecycleStagestringoptionalmax 100 chars. nullable
websitestringoptionalmax 500 chars. nullable
phonestringoptionalmax 30 chars. nullable
addressstringoptionalStreet address line. City, state, zip and country are their own top-level fields. max 300 chars. nullable
citystringoptionalmax 100 chars. nullable
statestringoptionalmax 100 chars. nullable
zipstringoptionalmax 20 chars. nullable
countryoptional
descriptionstringoptionalmax 2000 chars. nullable
customFieldsoptional
GET/api/v1/companies/:id

Retrieve a single company by ID, including linked contacts.

200 OK404 Not Found
PATCH/api/v1/companies/:id

Update company fields. Only include fields you want to change.

200 OK404 Not Found
DELETE/api/v1/companies/:id

Delete a company. Linked contacts are not deleted but the association is removed.

200 OK404 Not Found

Deals

Manage deals in your pipeline. Deals represent potential revenue and track through stages from qualification to close.

GET/api/v1/deals

List all deals with optional filters. Results are ordered by updated_at descending by default.

200 OK
ParameterDescription
assignedToFilter by assigned user ID
assigned_to
contact_id
created_after
created_before
maxValue
minValue
needsAction
order
pagePage number (default: 1)
per_page
pipeline
pipelineIdFilter by pipeline ID
pipelineName
pipeline_id
pipeline_name
search
sort
stage
stageIdFilter by stage ID
stageName
stage_id
stage_name
stale
statusFilter by status: open, won, lost
summary
tag_ids
value_max
value_min
view
viewId
view_id
200 OK
{
  "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 }
  }
}
POST/api/v1/deals

Create a new deal in a pipeline.

201 Created422 Validation
FieldDescription
titlestringrequiredDeal title. max 200 chars
pipelineIduuidrequiredPipeline to place the deal in. Resolve a pipeline name to its id with POST /api/v1/automations/resolve.
stageIduuidrequiredInitial stage within the pipeline
contactIduuidoptionalAssociated contact ID. nullable
companyIduuidoptionalAssociated company ID. nullable
valueunionoptionalDeal value as a decimal amount (not cents). nullable. has conditional cross-field requirements
currencystringoptionalISO 4217 currency code (e.g. GBP). Backend defaults to USD when omitted.. max 10 chars
statusenumoptionalOne of: open, won, lost
prioritystringoptionalmax 30 chars
sourcestringoptionalLead source of THIS opportunity (e.g. the campaign/trigger that produced it) — distinct from the contact's first-touch source. max 500 chars. nullable
lostReasonstringoptionalReason 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
probabilitynumberoptionalWin probability as a 0–1 fraction (e.g. 0.75 = 75%), not a percentage. range 0–1. nullable
expectedCloseDatestringoptionalExpected close date (YYYY-MM-DD). max 50 chars. nullable. has conditional cross-field requirements
appointmentAtISO 8601optionalAppointment 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
assignedTouuidoptionalUser ID to own this deal. nullable
customFieldsobjectoptionalKey-value pairs for custom fields. has conditional cross-field requirements
productsobject[]optionalmax 50 items. Each item: id (uuid), name (string, required), description (string), quantity (number), unitPrice (union, required), discount (union), tax (union), sortOrder (number)
curl
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"
  }'
GET/api/v1/deals/:id

Retrieve a single deal by ID, including pipeline, stage, contact, and company details.

200 OK404 Not Found
PATCH/api/v1/deals/:id

Update a deal. Move between stages by changing stageId. Changing status to "won" or "lost" closes the deal.

200 OK404 Not Found
curl · Move deal to next stage
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.

GET/api/v1/pipelines

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.

200 OK
200 OK
{
  "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
        }
      ]
    }
  ]
}
POST/api/v1/pipelines

Create a new pipeline with initial stages.

201 Created
FieldDescription
namestringrequiredPipeline name. max 200 chars
descriptionstringoptionalmax 2000 chars. nullable
isDefaultbooleanoptional
stagesobject[]optionalInitial stages, in order. max 50 items. Each item: name (string, required), order (number), color (string), isWon (boolean), isLost (boolean)
GET/api/v1/pipelines/:id

Retrieve a pipeline with its stages and summary statistics.

200 OK404 Not Found
PATCH/api/v1/pipelines/:id

Update pipeline name or settings.

200 OK
GET/api/v1/pipelines/:id/stages

List all stages in a pipeline, ordered by position.

200 OK
200 OK
{
  "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 }
  ]
}
POST/api/v1/pipelines/:id/stages

Add a new stage to a pipeline.

201 Created
FieldDescription
color
isLost
isWon
nameStage name
PATCH/api/v1/pipelines/:id/stages/:stageId

Update a stage name, position, or probability.

200 OK

Tasks

Manage tasks assigned to team members. Tasks can be linked to contacts or deals.

GET/api/v1/tasks

List all tasks with optional filters for status, assignee, and due date.

200 OK
ParameterDescription
assignedToFilter by assigned user
assigned_to
contactIdFilter by linked contact
contact_id
dealIdFilter by linked deal
deal_id
dueFrom
dueTo
order
overdue
page
per_page
priority
search
sort
statusFilter: todo, in_progress, done. Use overdue=true for overdue tasks
tab
200 OK
{
  "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 }
  }
}
POST/api/v1/tasks

Create a new task.

201 Created
FieldDescription
titlestringrequiredTask title. max 500 chars
descriptionstringoptionalTask description. max 5000 chars
dueDatestringoptionalDue date and time. has conditional cross-field requirements
priorityenumoptionalPriority: low, medium, high. One of: low, medium, high, urgent
statusenumoptionalOne of: todo, in_progress, done
assignedTounionoptionalAssigned user ID
contactIduuidoptionalLink to a contact. nullable
dealIduuidoptionalLink to a deal. nullable
GET/api/v1/tasks/:id

Retrieve a single task by ID.

200 OK404 Not Found
PATCH/api/v1/tasks/:id

Update a task. Status is one of todo, in_progress, done — set done to complete it.

200 OK
curl · Complete a task
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.

GET/api/v1/notes

List notes, optionally filtered by contact or deal.

200 OK
ParameterDescription
contact_id
deal_id
page
per_page
200 OK
{
  "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 }
  }
}
POST/api/v1/notes

Create a note on a contact or deal.

201 Created
FieldDescription
bodystringrequiredhas conditional cross-field requirements
contactIdstringoptionalAttach to a contact. max 100 chars. nullable
dealIdstringoptionalAttach to a deal. max 100 chars. nullable
isPinnedbooleanoptional
GET/api/v1/notes/:id

Retrieve a single note by ID.

200 OK404 Not Found
PATCH/api/v1/notes/:id

Update note content.

200 OK

Tags

Manage tags for segmenting and organizing contacts.

GET/api/v1/tags

List all tags in the workspace with contact counts.

200 OK
200 OK
{
  "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 }
}
POST/api/v1/tags

Create a new tag.

201 Created
FieldDescription
colorHex color code for display
nameTag name (unique per workspace)
GET/api/v1/tags/:id

Retrieve a single tag with its contact count.

200 OK404 Not Found
PATCH/api/v1/tags/:id

Update tag name or color.

200 OK

Messages

Send SMS and email messages to contacts. View message history and delivery status.

GET/api/v1/messages

List messages with optional filters for contact, channel, and direction.

200 OK
ParameterDescription
channelFilter: sms, email
contactIdFilter by contact
directionFilter: inbound, outbound
page
per_page
status
200 OK
{
  "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 }
  }
}
POST/api/v1/messages

Send a message to a contact via SMS or email.

201 Created422 Validation
FieldDescription
contactIdstringrequiredRecipient contact ID. max 100 chars
channelenumrequiredChannel: sms or email. One of: sms, email
directionenumrequiredOne of: inbound, outbound
bodystringrequiredMessage body (plain text for SMS, HTML for email). has conditional cross-field requirements
subjectstringoptionalEmail subject (required for email channel). max 500 chars. nullable
fromNumberstringoptionalmax 30 chars. nullable
toNumberstringoptionalmax 30 chars. nullable
fromEmailstringoptionalmax 254 chars. nullable
toEmailstringoptionalmax 254 chars. nullable
replyTostringoptionalmax 254 chars. nullable
ccunionoptionalnullable
bccunionoptionalnullable
bodyHtmlstringoptionalnullable
providerstringoptionalmax 50 chars. nullable
providerIdstringoptionalmax 200 chars. nullable
metadataunknownoptional
statusstringoptionalmax 30 chars
scheduledAtISO 8601optionalnullable
curl · Log an inbound SMS
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.

POST/api/v1/messages/sms/send

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.

201 Created400 transport_conflict422 Validation / compliance
FieldDescription
contactIduuidrequiredRecipient contact ID — the contact must have a phone number and no opt-out
bodystringoptionalSMS body, 1–1600 characters. max 1600 chars
fromNumberstringoptionalTwilio only: an account-owned number or agent DID; ignored for project_blue (the line is the sender). max 30 chars
toNumberstringoptionalmax 30 chars
smsProviderIduuidoptionalTwilio only: route through a configured external SMS provider; a 400 transport_conflict alongside project_blue
transportenumoptionaltwilio (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
idempotencyKeystringoptionalYour 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 · Text from the agent's iPhone line, safely retryable
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"
  }'
201 Created
{
  "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.

GET/api/v1/settings/project-blue

Connection status plus this account's line assignments. status is connected or not_configured; the stored key is always masked.

200 OK
200 OK
{
  "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 } }
    ]
  }
}
PUT/api/v1/settings/project-blue

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.

200 OK422 key_rejected502 vendor_unavailable
FieldDescription
apiKeystringrequiredThe Project Blue API key (proj_…); never echoed back. max 512 chars
DELETE/api/v1/settings/project-blue

Forget the key and release every agent's line together. Sends with transport: "project_blue" answer 422 project_blue_not_configured afterwards.

200 OK
GET/api/v1/settings/project-blue/lines

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.

200 OK409 not_configured422 key_rejected502 vendor_unavailable
200 OK
{
  "data": {
    "lines": [
      { "lineId": "fdac230c-6228-4560-817f-03378a7c964e", "phoneNumber": "+15513285489", "name": "Sales iPhone",
        "assignedTo": { "userId": "8e1b6c2a-1111-4111-8111-111111111111", "name": "Jordan Tate" } }
    ],
    "orphaned": []
  }
}
PUT/api/v1/settings/project-blue/lines/:lineId

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.

200 OK404 line_not_found404 user_not_found409 not_configured409 credentials_changed409 conflict
FieldDescription
userIduuidrequiredThe agent's user ID, or null to release the line. nullable
curl · Assign a line
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.

GET/api/v1/conversations

List all conversation threads with latest message preview.

200 OK
200 OK
{
  "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/api/v1/conversations/:contactId

Get the full conversation thread for a specific contact, with all messages across channels.

200 OK404 Not Found

Calendars

Manage calendars and appointments. Create booking links, schedule meetings, and track availability.

GET/api/v1/calendars

List all calendars in the workspace.

200 OK
POST/api/v1/calendars

Create a new calendar with availability settings.

201 Created
FieldDescription
description
isDefault
nameCalendar name
timezoneIANA timezone (e.g., America/New_York)
GET/api/v1/calendars/:id

Retrieve calendar details and availability settings.

200 OK404 Not Found
PATCH/api/v1/calendars/:id

Update calendar settings.

200 OK
GET/api/v1/calendars/:id/appointments

List appointments for a calendar with optional date range filter.

200 OK
ParameterDescription
assigned_to
contact_id
page
per_page
start_after
start_before
statusFilter: scheduled, completed, cancelled
200 OK
{
  "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 }
  }
}
POST/api/v1/calendars/:id/appointments

Schedule a new appointment on a calendar.

201 Created409 Conflict
FieldDescription
assignedTo
contactIdLink to a contact
description
endTimeEnd date and time
location
metadata
startTimeStart date and time
status
titleAppointment 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.

GET/api/v1/custom-fields

List all custom field definitions.

200 OK
ParameterDescription
entityType
page
per_page
200 OK
{
  "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 }
}
POST/api/v1/custom-fields

Define a new custom field.

201 Created422 Validation
FieldDescription
entityTypeenumrequiredOne of: contact, deal, company
fieldKeystringrequired
labelstringrequiredmax 200 chars
fieldTypeenumrequiredOne of: text, textarea, number, date, datetime, url, select, radio, multiselect, boolean, phone, email
sectionstringoptionalmax 120 chars. nullable
optionsunionoptionalUse {"{ 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
sortOrdernumberoptionalrange 0–100000
PATCH/api/v1/custom-fields/:id

Update a custom field definition. Changing type is not allowed if data exists.

200 OK422 Validation
DELETE/api/v1/custom-fields/:id

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.

200 OK409 Impact Preflight
Request Body (confirmation retry)
FieldDescription
confirmImpactSet true to confirm the deletion after reviewing the 409 preflight
expectedDependencyCountThe dependency count from the preflight — must still match
expectedValueCountThe value count from the preflight — must still match at delete time
200 OK · cleanup queued
{
  "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.

GET/api/v1/webhooks/manage

List all registered webhook endpoints.

200 OK
200 OK
{
  "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 }
  }
}
POST/api/v1/webhooks/manage

Register a new webhook endpoint.

201 Created422 Validation
FieldDescription
descriptionInternal label for the endpoint
eventsArray of event names to subscribe to
isActiveWhether the webhook is active (default: true)
urlHTTPS endpoint URL
curl
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
  }'
GET/api/v1/webhooks/manage/:id

Retrieve webhook details including delivery history.

200 OK404 Not Found
PATCH/api/v1/webhooks/manage/:id

Update webhook URL, events, or active status.

200 OK
DELETE/api/v1/webhooks/manage/:id

Archive a webhook registration and cancel pending deliveries for that endpoint.

200 OK
POST/api/v1/webhooks/manage/:id/test

Send a test payload to the webhook URL to verify it is receiving and processing events correctly.

200 OK502 Delivery Failed
200 OK
{
  "success": true,
  "statusCode": 200,
  "responseTime": 142,
  "event": "test.ping"
}
GET/api/v1/webhooks/manage/:id/deliveries

List recent delivery attempts for one endpoint. Payload fields with contact PII are redacted unless the caller has full contact visibility.

200 OK
POST/api/v1/webhooks/replay

Replay failed or selected webhook deliveries. Test and replay actions require an active account and owner/admin access.

200 OK402 Billing Required

Users

Manage team members in the workspace. Invite new users, assign roles, and remove access.

GET/api/v1/users

List all users in the workspace.

200 OK
200 OK
{
  "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 }
  }
}
POST/api/v1/users/invite

Send an invitation email to add a new user to the workspace.

201 Created422 Validation
FieldDescription
emailEmail address to invite
extension
firstName
first_name
lastName
last_name
permissions
phone
roleRole: member (default) or admin. Any other value, including viewer, is rejected with 422.
GET/api/v1/users/:id

Retrieve a single user by ID.

200 OK404 Not Found
PATCH/api/v1/users/:id

Update a user's role or name. Requires admin scope.

200 OK403 Forbidden
DELETE/api/v1/users/:id

Remove a user from the workspace. Their assigned contacts and deals are unassigned.

200 OK403 Forbidden

Automations

Manage n8n-powered automations. Register webhook triggers and fire custom events into your workflows.

GET/api/v1/automations

List all automation configurations.

200 OK
200 OK
{
  "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
    }
  ]
}
POST/api/v1/automations

Create a new automation with a webhook trigger.

201 Created
FieldDescription
actions
description
folderId
graph
graphVersion
kind
n8nWebhookUrl
n8nWorkflowId
nameAutomation name
nodes
schedule
scheduleTimezone
startNodeId
triggerEvent that triggers this automation
triggerConditions
triggerEvent
GET/api/v1/automations/:id

Retrieve automation details and run history.

200 OK404 Not Found
GET/api/v1/automations/events

List every trigger event automations can listen for, with its payload description. Use this to discover valid trigger values before creating an automation.

200 OK
Query Parameters
ParameterDescription
kindFilter to events available for one runner kind: native or n8n. Omit for all.
curl
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.

POST/api/v1/bulk/contacts/update

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.

200 OK400 Validation422 Too many
FieldDescription
updatesOne 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
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" }
    ]
  }'
200 OK
{
  "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" }
    ]
  }
}
POST/api/v1/bulk/contacts/delete

Batch delete multiple contacts. Returns AGGREGATE counts rather than a per-row list.

200 OK400 Validation422 Too many
FieldDescription
idsArray of contact IDs to delete (max 100)
POST/api/v1/bulk/contacts/tag

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.

200 OK400 Validation422 Too many
FieldDescription
contactIdsArray of contact IDs (max 100)
tagIdThe id of the tag to apply
POST/api/v1/bulk/deals/update

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.

200 OK400 Validation422 Too many
FieldDescription
updatesOne object per deal, each with its id plus the fields to change (max 100 per request)

Activities

Audit trail of all actions in the workspace. Every create, update, delete, and login is logged.

GET/api/v1/activities

List activity entries with optional filters. Ordered by timestamp descending.

200 OK
ParameterDescription
contact_id
deal_id
fromDate
page
per_page
search
toDate
type
200 OK
{
  "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.

POST/api/v1/billing/checkout

Create a Stripe checkout session for first-time billing, or update an existing subscription. Existing subscription changes may be prorated by Stripe immediately.

200 OK
FieldDescription
intervalBilling interval: monthly or yearly
planIdPlan ID: standard, premium, or enterprise
200 OK - checkout session
{
  "data": {
    "url": "https://checkout.stripe.com/c/pay/cs_live_..."
  }
}
200 OK - existing subscription updated
{
  "data": {
    "action": "subscription_updated",
    "subscriptionId": "sub_1Oa2b3c4d5e6f7g8",
    "plan": "premium",
    "status": "active"
  }
}
POST/api/v1/billing/portal

Generate a Stripe Customer Portal URL for managing the subscription, payment methods, and invoices. Requires an existing Stripe customer.

200 OK
200 OK
{
  "data": {
    "url": "https://billing.stripe.com/p/session/..."
  }
}
GET/api/v1/billing/status

Get the current subscription status, billing health, usage, and recent invoices for the workspace.

200 OK
200 OK
{
  "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.

GET/api/v1/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.

200 OK
200 OK · abridged
{
  "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.

POST/api/v1/auth/login

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.

200 OK401 Invalid Credentials
FieldDescription
emailstringrequiredUser email address
passwordstringrequiredUser password
accountIdstringoptional
200 OK · session cookie in Set-Cookie
{
  "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
  }
}
200 OK · MFA challenge (no session yet)
{
  "data": {
    "mfaRequired": true,
    "mfaToken": "..."
  }
}
POST/api/v1/auth/register

Create a new account and workspace.

200 OK400 Validation
FieldDescription
accountName
emailEmail address
firstName
lastName
passwordPassword (min 8 characters)
POST/api/v1/auth/logout

Invalidate the current session token.

200 OK
GET/api/v1/auth/me

Get the current authenticated user and workspace details.

200 OK401 Unauthorized
200 OK
{
  "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.

GET/api/v1/forms

List all forms in the workspace with submission counts.

200 OK401 Unauthorized
Query Parameters
ParameterDescription
include_archived
is_active
pagePage number (default: 1)
per_page
searchSearch by form name
Response
200 OK
{
  "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 }
  }
}
Example
curl
curl -H "Authorization: Bearer cdy_your_api_key" \
  "https://conduyt.app/api/v1/forms"
POST/api/v1/forms

Create a new lead capture form with custom fields and settings.

201 Created422 Validation
Request Body
FieldDescription
descriptionDescription of the form&apos;s purpose
fieldsArray of field definitions. Each field: key, label, type (text, email, phone, textarea, select, number), required (boolean), options (for select type)
hyrosTag
isActive
nameForm name (internal label)
redirectUrlURL the thank-you screen redirects to after a successful submission
settingsOptional 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
thankYouMessageMessage shown when no redirectUrl is set
Example
curl
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"]
    }
  }'
GET/api/v1/forms/:id

Retrieve a single form by ID, including its field definitions and submission count.

200 OK404 Not Found
curl
curl -H "Authorization: Bearer cdy_your_api_key" \
  https://conduyt.app/api/v1/forms/a1b2c3d4-e5f6-4890-abcd-ef1234567890
PATCH/api/v1/forms/:id

Update a form. Only include fields you want to change.

200 OK404 Not Found422 Validation
curl
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"] } }'
DELETE/api/v1/forms/:id

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.

200 OK404 Not Found
curl
curl -X DELETE -H "Authorization: Bearer cdy_your_api_key" \
  https://conduyt.app/api/v1/forms/a1b2c3d4-e5f6-4890-abcd-ef1234567890
200 OK
{
  "data": { "id": "a1b2c3d4-e5f6-4890-abcd-ef1234567890", "archived": true }
}
GET/api/v1/forms/:id/submissions

List all submissions for a form, paginated. Each submission includes the submitted data and the auto-created contact reference.

200 OK404 Not Found
Query Parameters
ParameterDescription
pagePage number (default: 1)
per_page
Response
200 OK
{
  "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 }
  }
}
Example
curl
curl -H "Authorization: Bearer cdy_your_api_key" \
  "https://conduyt.app/api/v1/forms/a1b2c3d4-e5f6-4890-abcd-ef1234567890/submissions?per_page=25"
POST/api/v1/forms/:id/submit

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.

201 Created400 Validation404 Not Found
Request Body
FieldDescription
_hp_fieldHoneypot — leave empty. A non-empty value silently discards the submission (bot protection).
attribution
consent
dataKey-value pairs matching the form's field keys. Required fields must be present.
source
utm
Example
curl · No auth required
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"
    }
  }'
Response
201 Created
{
  "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.

GET/api/v1/products

List all products with optional search and active/inactive filtering.

200 OK401 Unauthorized
Query Parameters
ParameterDescription
is_active
pagePage number (default: 1)
per_page
searchSearch by product name or SKU
Response
200 OK
{
  "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 }
  }
}
Example
curl
curl -H "Authorization: Bearer cdy_your_api_key" \
  "https://conduyt.app/api/v1/products?is_active=true&search=CRM"
POST/api/v1/products

Create a new product in the catalog.

201 Created400 Validation
Request Body
FieldDescription
currency
descriptionProduct description
isActive
metadata
nameProduct name
priceUnit price in dollars
skuStock keeping unit identifier
taxableWhether tax applies (default: true)
unitUnit of measure (e.g., hour, project, month, each)
Example
curl
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
  }'
GET/api/v1/products/:id

Retrieve a single product by ID.

200 OK404 Not Found
curl
curl -H "Authorization: Bearer cdy_your_api_key" \
  https://conduyt.app/api/v1/products/7f8e9d0c-1b2a-4456-adef-789012345678
PATCH/api/v1/products/:id

Update a product. Only include fields you want to change.

200 OK404 Not Found400 Validation
curl
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" }'
DELETE/api/v1/products/:id

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 } }.

200 OK404 Not Found
curl
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.

GET/api/v1/invoices

List all invoices with optional filters for status, contact, and date range.

200 OK401 Unauthorized
Query Parameters
ParameterDescription
contact_id
date_from
date_to
pagePage number (default: 1)
per_page
search
statusFilter by status: draft, sent, paid, overdue, void
Response
200 OK
{
  "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 }
  }
}
Example
curl
curl -H "Authorization: Bearer cdy_your_api_key" \
  "https://conduyt.app/api/v1/invoices?status=sent&date_to=2026-05-01"
POST/api/v1/invoices

Create a new invoice with line items. Totals are auto-calculated from item quantities and unit prices.

201 Created400 Validation
Request Body
FieldDescription
companyId
contactIdContact to invoice. Optional — the handler persists null when omitted; only a non-empty items array is required.
currency
dealId
dueDatePayment due date. Default: 30 days from creation.
invoiceNumber
itemsLine items. Each: description (string), quantity (number), unitPrice (number), optional productId (uuid)
notesNotes displayed on the invoice
taxRateTax rate as decimal (e.g., 0.0875 for 8.75%). Default: workspace setting.
terms
Example
curl
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."
  }'
Response
201 Created
{
  "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"
  }
}
GET/api/v1/invoices/:id

Retrieve a single invoice with all line items and payment history.

200 OK404 Not Found
Response
200 OK
{
  "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"
  }
}
Example
curl
curl -H "Authorization: Bearer cdy_your_api_key" \
  https://conduyt.app/api/v1/invoices/1c4d5e6f-7a8b-4c9d-8e0f-1a2b3c4d5e60
PATCH/api/v1/invoices/:id

Update a draft invoice. Sent and paid invoices cannot be edited. Void and recreate instead.

200 OK404 Not Found400 Validation409 Conflict
curl
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" }'
POST/api/v1/invoices/:id/send

Mark an invoice as sent. Changes status from "draft" to "sent" and records the sent timestamp.

200 OK404 Not Found
curl
curl -X POST -H "Authorization: Bearer cdy_your_api_key" \
  https://conduyt.app/api/v1/invoices/1c4d5e6f-7a8b-4c9d-8e0f-1a2b3c4d5e60/send
Response
200 OK
{
  "data": {
    "id": "1c4d5e6f-7a8b-4c9d-8e0f-1a2b3c4d5e60",
    "invoiceNumber": "INV-001",
    "status": "sent",
    "sentAt": "2026-04-19T10:30:00Z"
  }
}
POST/api/v1/invoices/:id/void

Void an invoice. Voided invoices cannot be edited or paid. This action cannot be undone.

200 OK404 Not Found409 Conflict
curl
curl -X POST -H "Authorization: Bearer cdy_your_api_key" \
  https://conduyt.app/api/v1/invoices/1c4d5e6f-7a8b-4c9d-8e0f-1a2b3c4d5e60/void
GET/api/v1/invoices/:id/payments

List all payments recorded against an invoice.

200 OK404 Not Found
Response
200 OK
{
  "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"
    }
  ]
}
Example
curl
curl -H "Authorization: Bearer cdy_your_api_key" \
  https://conduyt.app/api/v1/invoices/1c4d5e6f-7a8b-4c9d-8e0f-1a2b3c4d5e60/payments
POST/api/v1/invoices/:id/payments

Record a payment against an invoice. Automatically updates the invoice's amountPaid and transitions status to "paid" when fully paid.

201 Created400 Validation
Request Body
FieldDescription
amountPayment amount in dollars
methodPayment method: credit_card, bank_transfer, check, cash, other
notesInternal notes about this payment
paidAt
referenceExternal reference (check number, wire ref, etc.)
Example
curl
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"
  }'
Response
201 Created
{
  "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}}.

GET/api/v1/emails/templates

List all email templates in the workspace.

200 OK401 Unauthorized
Query Parameters
ParameterDescription
category
channel
is_active
pagePage number (default: 1)
per_page
provider
Response
200 OK
{
  "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 }
}
Example
curl
curl -H "Authorization: Bearer cdy_your_api_key" \
  "https://conduyt.app/api/v1/emails/templates?channel=email"
POST/api/v1/emails/templates

Create a new email template with HTML content and merge fields.

201 Created400 Validation
Request Body
FieldDescription
bodyRequired. Plain-text body (supports merge fields)
bodyHtmlHTML email body (supports merge fields)
category
channelRequired. sms or email
isActive
nameRequired. Internal template name
provider
subjectEmail subject line (supports merge fields)
Example
curl
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>"
  }'
GET/api/v1/emails/templates/:id

Retrieve a single email template by ID.

200 OK404 Not Found
curl
curl -H "Authorization: Bearer cdy_your_api_key" \
  https://conduyt.app/api/v1/emails/templates/a1b2c3d4-5678-4012-abcd-ef3456789012
PATCH/api/v1/emails/templates/:id

Update an email template. Only include fields you want to change.

200 OK404 Not Found400 Validation
curl
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}}" }'
DELETE/api/v1/emails/templates/:id

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 } }.

200 OK404 Not Found409 Conflict
curl
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.

GET/api/v1/emails/sequences

List all email sequences with enrollment stats.

200 OK401 Unauthorized
Response
200 OK
{
  "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 }
  }
}
Example
curl
curl -H "Authorization: Bearer cdy_your_api_key" \
  "https://conduyt.app/api/v1/emails/sequences"
POST/api/v1/emails/sequences

Create a new email sequence with ordered steps. Each step references a template and a delay (in days) from enrollment.

201 Created422 Validation
Request Body
FieldDescription
autoUnenrollOnBounce
autoUnenrollOnMeeting
autoUnenrollOnReply
autoUnenrollOnUnsubscribe
description
isActive
nameSequence name
stepsOrdered steps. Each: templateId (uuid), delayDays (integer, days after enrollment), optional subject override
Example
curl
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" }
    ]
  }'
GET/api/v1/emails/sequences/:id

Retrieve a single sequence with its steps and enrollment statistics.

200 OK404 Not Found
curl
curl -H "Authorization: Bearer cdy_your_api_key" \
  https://conduyt.app/api/v1/emails/sequences/b2c3d4e5-f6a7-4901-bcde-f23456789012
PATCH/api/v1/emails/sequences/:id

Update a sequence. Modifying steps on an active sequence only affects future enrollments. Contacts already in progress continue on the original steps.

200 OK404 Not Found422 Validation
curl
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" }'
POST/api/v1/emails/sequences/:id/enroll

Enroll one or more contacts into a sequence. Contacts already enrolled are silently skipped.

201 Created200 OK404 Not Found422 Validation
Request Body
FieldDescription
contactIdsArray of contact IDs to enroll (max 100 per request)
Example
curl
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"] }'
Response
200 OK
{
  "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" }
  ]
}
POST/api/v1/emails/sequences/:id/unenroll

Remove a contact from a sequence. Stops all future emails in the sequence for this contact.

200 OK404 Not Found
Request Body
FieldDescription
contactIdContact ID to unenroll
Example
curl
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" }'
GET/api/v1/emails/sequences/:id/enrollments

List all enrollments for a sequence, including current step and completion status.

200 OK404 Not Found
Query Parameters
ParameterDescription
pagePage number (default: 1)
per_page
statusFilter by enrollment status: active, paused, completed, cancelled, unsubscribed, failed
Response
200 OK
{
  "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 }
  }
}
Example
curl
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.

GET/api/v1/calls

List all logged calls with optional filters.

200 OK401 Unauthorized
Query Parameters
ParameterDescription
contact_id
date_from
date_to
directionFilter by direction: inbound, outbound
minimal
pagePage number (default: 1)
per_page
statusFilter by status: completed, missed, voicemail, no_answer
user_id
Response
200 OK
{
  "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 }
  }
}
Example
curl
curl -H "Authorization: Bearer cdy_your_api_key" \
  "https://conduyt.app/api/v1/calls?direction=outbound&status=completed&date_from=2026-04-01"
POST/api/v1/calls

Log a new call. Creates an entry in the contact's activity timeline.

201 Created400 Validation
Request Body
FieldDescription
contactIdContact the call is with
directionCall direction: inbound or outbound
disposition
durationCall duration in seconds
fromNumberRequired unless source is "manual_log"
notesCall notes or summary
sourceSend "manual_log" when logging a call by hand. WITHOUT it the call is treated as a telephony record and fromNumber + toNumber become required.
statusCall outcome: completed, missed, voicemail, no_answer
toNumberRequired unless source is "manual_log"
Example
curl
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."
  }'
GET/api/v1/calls/:id

Retrieve a single call log entry by ID.

200 OK404 Not Found
curl
curl -H "Authorization: Bearer cdy_your_api_key" \
  https://conduyt.app/api/v1/calls/d4e5f6a7-b8c9-4123-8ef4-567890123456
PATCH/api/v1/calls/:id

Update a call log entry. Commonly used to add notes after a call or correct the status.

200 OK404 Not Found400 Validation
curl
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.

Graph shape
draftGraph
{
  "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_missing
GET/api/v1/call-flows

List call flows with their draft and published graphs, draftRevision, and today's call count.

200 OK401 Unauthorized
POST/api/v1/call-flows

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.

FieldDescription
draftGraphOptional { entryId, nodes[] }. Omitted → seeded entry → voicemail. Supplied → validated like PATCH (blank/duplicate ids, unrenderable node shapes, or null → 422)
nameDisplay name
phoneNumberE.164 number this flow will answer (optional; claim-checked)
201 Created409 Number already answered422 Malformed draftGraph
GET/api/v1/call-flows/:id

The full flow row, including draftRevision and updatedAt (both needed for writes).

PATCH/api/v1/call-flows/:id

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.

Example
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 } ] } }'
200 OK409 Conflict428 expectedDraftRevision required
GET/api/v1/call-flows/:id/validate

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.

POST/api/v1/call-flows/:id/publish

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).

200 Published422 Blockers409 Stale draft428 expectedUpdatedAt required
POST/api/v1/call-flows/:id/unpublish

Take the flow off its number. Calls in progress finish on their pinned version; new calls fall through to standard routing.

POST/api/v1/call-flows/:id/simulate

Walk the draft against live presence: who would ring, in what order, and where the call ends if nobody answers.

GET/api/v1/call-flows/roster

Active team members with their phone (did) and presence (on, call, off). Members without a phone cannot be placed in a flow.

GET/api/v1/call-flows/numbers

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.

200 OK502 Twilio unreachable
GET/api/v1/call-flows/:id/wiring

What Twilio has on the flow's number right now versus the inboundUrl Conduyt needs.

POST/api/v1/call-flows/:id/wiring

Point the number's Twilio voice webhook at Conduyt (the same step publish performs). Admin. Returns the resulting wiring state; 502 when Twilio refused.

POST/api/v1/call-flows/tombstones/release

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.

200 OK404 / 409 / 502

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.

GET/api/v1/workflows

List all workflows in the workspace.

200 OK401 Unauthorized
Query Parameters
ParameterDescription
is_active
pagePage number (default: 1)
per_page
Response
200 OK
{
  "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 }
  }
}
Example
curl
curl -H "Authorization: Bearer cdy_your_api_key" \
  "https://conduyt.app/api/v1/workflows?is_active=true"
POST/api/v1/workflows

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.

201 Created400 Validation
Request Body
FieldDescription
actionsOrdered actions. Each: type (assign_contact, send_email, create_task, update_field, send_webhook, enroll_sequence), config (object)
descriptionDescription of what this workflow does
isActive
nameWorkflow name
triggerTrigger definition: event (string — e.g., contact.created, deal.stage_changed, form.submitted), optional conditions (object)
Example
curl
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" } }
    ]
  }'
GET/api/v1/workflows/:id

Retrieve a single workflow with its trigger, actions, and run statistics.

200 OK404 Not Found
curl
curl -H "Authorization: Bearer cdy_your_api_key" \
  https://conduyt.app/api/v1/workflows/e5f6a7b8-c9d0-4234-8f56-789012345678
PATCH/api/v1/workflows/:id

Update a workflow. Active workflows can be updated. Changes take effect on the next trigger event.

200 OK404 Not Found400 Validation
curl
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/api/v1/workflows/:id

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 } }.

200 OK404 Not Found
curl
curl -X DELETE -H "Authorization: Bearer cdy_your_api_key" \
  https://conduyt.app/api/v1/workflows/e5f6a7b8-c9d0-4234-8f56-789012345678
POST/api/v1/workflows/:id/activate

Activate a workflow. Once active, the workflow will fire on matching trigger events.

200 OK404 Not Found
curl
curl -X POST -H "Authorization: Bearer cdy_your_api_key" \
  https://conduyt.app/api/v1/workflows/e5f6a7b8-c9d0-4234-8f56-789012345678/activate
Response
200 OK
{
  "data": {
    "id": "e5f6a7b8-c9d0-4234-8f56-789012345678",
    "name": "Deal Won — Full Onboarding Flow",
    "status": "active",
    "activatedAt": "2026-04-19T12:00:00Z"
  }
}
POST/api/v1/workflows/:id/deactivate

Deactivate a workflow. Stops it from firing on new events. In-flight runs continue to completion.

200 OK404 Not Found
curl
curl -X POST -H "Authorization: Bearer cdy_your_api_key" \
  https://conduyt.app/api/v1/workflows/e5f6a7b8-c9d0-4234-8f56-789012345678/deactivate
GET/api/v1/workflows/:id/runs

List execution history for a workflow. Each run represents one trigger event and the resulting action executions.

200 OK404 Not Found
Query Parameters
ParameterDescription
pagePage number (default: 1)
per_page
statusFilter by run status: success, failed, running
Response
200 OK
{
  "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 }
  }
}
Example
curl
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/api/v1/workflows/:id/runs/:runId

Get full details of a specific workflow run, including per-action results and any error messages.

200 OK404 Not Found
Response
200 OK
{
  "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
  }
}
Example
curl
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.