Every API here is available over the APIs.io API and to AI agents over MCP.
openapi: 3.2.0
info:
title: Conduyt Pipelines API
version: 1.0.0
description: 'The Conduyt CRM API provides programmatic access to your CRM data including
contacts, companies, deals, pipelines, tasks, notes, automations, invoices,
email sequences, and more.
## Authentication
All authenticated endpoints require a Bearer token (API key) in the
`Authorization` header:
```
Authorization: Bearer cdy_
```
API keys are generated in **Settings > API Keys** within the Conduyt
dashboard. Keys use the `cdy_` prefix and are hashed with bcrypt on
creation — the full key is shown exactly once at creation time.
Session cookie authentication (`conduyt_session`) is also supported for
browser-based clients but is not recommended for integrations.
## Rate Limiting
API keys enforce a per-key rate limit. New keys default to 100 requests per
minute and can be configured at creation time or with PATCH `/api-keys/{id}`.
Some public and provider-facing endpoints also enforce stricter per-IP or
per-account limits. When exceeded, the API returns `429 Too Many Requests`
with a `Retry-After` header indicating seconds until the next allowed
request.
| Endpoint Group | Limit |
|---------------------|---------------------|
| API keys | Default 100 / min per key |
| Login/reset | 5 / min per IP |
| Registration | 3 / hour per IP |
| Contact/Deal create | 30 / min per IP |
| Inbound messages | 120 / min per account/IP |
## Pagination
List endpoints support cursor-based pagination via query parameters:
- `page` — Page number (default: 1)
- `per_page` — Items per page (default: 50, max: 200)
- `limit` — Accepted as an alias of `per_page` on `GET /contacts`; if both are
sent they must carry the same value.
A page size above the maximum, a non-integer, a value below 1, or a
pagination key repeated with different values is rejected with `400`
(the message names the parameter and the rule); nothing is silently
clamped or ignored.
Responses include a `meta` object: `{ page, per_page, total }`.
An operation that documents its own pagination rules follows those instead.
`GET /sms-providers/options` pages with an opaque `cursor` and a `limit`
that is capped at 50 rather than rejected; its responses carry
`meta: { nextCursor, truncated }`.
## Response Format
All successful responses wrap data in a `data` envelope:
```json
{ "data": { ... } }
```
Error responses use:
```json
{ "error": "Human-readable error message" }
```
## Multi-Tenancy
Conduyt is multi-tenant. All data is scoped to the authenticated user''s
current account. API keys are bound to a specific account at creation time.
## Webhook Events
Outbound webhooks fire on events including:
`contact.created`, `contact.updated`, `contact.deleted`,
`deal.created`, `deal.updated`, `deal.won`, `deal.lost`,
`task.created`, `task.completed`, `note.created`,
`appointment.created`, `appointment.updated`,
`form.submitted`, `invoice.paid`
Payloads are signed with HMAC-SHA256 using the webhook''s secret. Retry
schedule: 1m, 5m, 30m, 2h, 12h, 72h (7 total attempts including the initial delivery).'
contact:
name: Conduyt Support
url: https://conduyt.app
email: support@conduyt.app
license:
name: Proprietary
url: https://conduyt.app/terms
servers:
- url: https://conduyt.app/api/v1
description: Production
security:
- BearerAuth: []
tags:
- name: Pipelines
description: Sales pipeline and stage management
paths:
/pipelines:
get:
operationId: listPipelines
summary: List pipelines with stages
tags:
- Pipelines
responses:
'200':
description: All pipelines with their stages
content:
application/json:
schema:
$ref: '#/components/schemas/DataEnvelope'
parameters:
- name: archived
in: query
required: false
description: 'true (owner or admin): list the ARCHIVED pipelines instead, as { id, name (the name before archiving), archivedName, color, archivedAt, openDeals, stageCount }, newest first, at most 200'
schema:
type: boolean
- name: page
in: query
required: false
description: Page number
schema:
type: integer
default: 1
minimum: 1
x-default-provenance: handler_literal
- name: per_page
in: query
required: false
description: Items per page (max 200)
schema:
type: integer
default: 50
minimum: 1
maximum: 100
x-default-provenance: handler_literal
post:
operationId: createPipeline
summary: Create a pipeline
description: Requires owner or admin role. Subject to plan limits.
tags:
- Pipelines
requestBody:
required: true
content:
application/json:
schema:
type: object
required:
- name
properties:
name:
type: string
minLength: 1
maxLength: 200
description: Must not start with "[Archived]" (archive with DELETE); otherwise a 400.
description:
anyOf:
- type: string
maxLength: 2000
- type: 'null'
isDefault:
type: boolean
color:
type: string
description: 'The pipeline''s colour, the dot next to its name in the board''s pipeline menu (any case accepted, stored in this spelling). Omitted: the first palette colour no active pipeline of the account uses. One of the pipeline palette colours (matched case-insensitively); any other value is refused.'
stages:
maxItems: 50
type: array
items:
type: object
properties:
name:
type: string
minLength: 1
maxLength: 200
order:
type: integer
minimum: 0
maximum: 9007199254740991
color:
type: string
maxLength: 50
isWon:
type: boolean
isLost:
type: boolean
isSystem:
type: boolean
required:
- name
description: Stage names must be unique (trimmed, case-insensitive), and a system holding stage cannot be won or lost; otherwise a 400. A stage cannot be both won and lost (isWon and isLost both true is refused).
responses:
'201':
description: Pipeline created with stages
content:
application/json:
schema:
$ref: '#/components/schemas/DataEnvelope'
'402':
description: Trial expired
'403':
description: Pipeline limit reached or insufficient role
parameters:
- name: dry_run
in: query
required: false
description: Validate the mutation without persisting changes.
schema:
type: boolean
- name: sandbox
in: query
required: false
description: Run the mutation in sandbox mode.
schema:
type: boolean
/pipelines/reorder:
post:
operationId: reorderPipelines
summary: Reorder the pipelines (board menu order), atomically
description: Writes every active pipeline's position in ONE transaction. order must list every active pipeline id exactly once; expectedRevision is the order the caller last read (the ids by position, comma-joined, as GET /pipelines returns them). Owner / admin.
tags:
- Pipelines
parameters:
- name: dry_run
in: query
required: false
description: Validate the mutation without persisting changes.
schema:
type: boolean
- name: sandbox
in: query
required: false
description: Run the mutation in sandbox mode.
schema:
type: boolean
requestBody:
required: true
content:
application/json:
schema:
type: object
required:
- order
- expectedRevision
properties:
order:
type: array
items:
type: string
format: uuid
description: Every active pipeline id, in the new order
expectedRevision:
type: string
description: The current order as the caller read it (ids comma-joined); a mismatch is a 409
responses:
'200':
description: 'The canonical order and its new revision: { order, revision }'
content:
application/json:
schema:
$ref: '#/components/schemas/DataEnvelope'
'400':
description: order is not a complete permutation of the active pipelines
'409':
description: 'order_changed: the order moved since the caller read it (revision carries the current one); nothing written'
/pipelines/{id}:
get:
operationId: getPipeline
summary: Get a pipeline by ID
tags:
- Pipelines
parameters:
- $ref: '#/components/parameters/IdParam'
responses:
'200':
description: Pipeline with stages
content:
application/json:
schema:
$ref: '#/components/schemas/DataEnvelope'
'404':
$ref: '#/components/responses/NotFound'
patch:
operationId: updatePipeline
summary: Update a pipeline
tags:
- Pipelines
parameters:
- $ref: '#/components/parameters/IdParam'
- name: dry_run
in: query
required: false
description: Validate the mutation without persisting changes.
schema:
type: boolean
- name: sandbox
in: query
required: false
description: Run the mutation in sandbox mode.
schema:
type: boolean
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
name:
type: string
description:
type: string
automationOnly:
type: boolean
description: 'Automation-only pipeline: cards in it are moved and edited only by automations, API keys and owners / admins; a member''s edit is refused with 403 pipeline_automation_only. Owner / admin only.'
color:
type: string
enum:
- '#3845C9'
- '#43DFF6'
- '#2DD6B5'
- '#22C58B'
- '#9BD13B'
- '#F2B544'
- '#F07A2F'
- '#E6465A'
- '#FD88C0'
- '#956EFA'
description: The pipeline's colour, the dot next to its name in the board's pipeline menu (any case accepted, stored in this spelling); anything else is a 400.
sortOrder:
type: integer
minimum: 0
description: The pipeline's position in the board's pipeline menu (ascending). Reorder by sending each moved pipeline its new position.
isDefault:
description: Read by the handler from the JSON body (the route declares no schema for its type).
responses:
'200':
description: Pipeline updated
content:
application/json:
schema:
$ref: '#/components/schemas/DataEnvelope'
'404':
$ref: '#/components/responses/NotFound'
delete:
operationId: deletePipeline
summary: Delete a pipeline
tags:
- Pipelines
parameters:
- $ref: '#/components/parameters/IdParam'
- name: dry_run
in: query
required: false
description: Validate the mutation without persisting changes.
schema:
type: boolean
- name: force
in: query
required: false
description: Query parameter read by this route.
schema:
type: boolean
- name: sandbox
in: query
required: false
description: Run the mutation in sandbox mode.
schema:
type: boolean
responses:
'200':
description: 'Pipeline deleted, or archived ({ id, archived: true }); a pipeline another request archived first answers { id, archived: true, alreadyArchived: true } untouched'
content:
application/json:
schema:
oneOf:
- type: object
properties:
data:
type: object
properties:
id: {}
archived:
type: boolean
alreadyArchived:
type: boolean
required:
- id
- archived
required:
- data
- type: object
properties:
data:
type: object
properties:
id: {}
deleted:
type: boolean
required:
- id
- deleted
required:
- data
'404':
$ref: '#/components/responses/NotFound'
'409':
description: 'Refused, nothing changed: active deals without force, a live unattended-lead sweep or the DPP feed on the pipeline, or intake_target (enabled intake creates deal cards in this pipeline)'
/pipelines/{id}/stages:
get:
operationId: listPipelineStages
summary: List stages for a pipeline
tags:
- Pipelines
parameters:
- $ref: '#/components/parameters/IdParam'
responses:
'200':
description: Ordered list of stages
content:
application/json:
schema:
$ref: '#/components/schemas/DataEnvelope'
post:
operationId: createPipelineStage
summary: Add a stage to a pipeline
tags:
- Pipelines
parameters:
- $ref: '#/components/parameters/IdParam'
- name: dry_run
in: query
required: false
description: Validate the mutation without persisting changes.
schema:
type: boolean
- name: sandbox
in: query
required: false
description: Run the mutation in sandbox mode.
schema:
type: boolean
requestBody:
required: true
content:
application/json:
schema:
type: object
required:
- name
properties:
name:
type: string
color:
type: string
isWon:
type: boolean
isLost:
type: boolean
isSystem:
description: Read by the handler from the JSON body (the route declares no schema for its type).
responses:
'201':
description: Stage created
content:
application/json:
schema:
$ref: '#/components/schemas/DataEnvelope'
'409':
description: 'pipeline_archived: the pipeline was archived while this request waited ("This pipeline was archived meanwhile."); nothing created'
/pipelines/{id}/stages/reorder:
post:
operationId: reorderPipelineStages
summary: Reorder a pipeline's stages (board columns), atomically
description: Writes every stage's position in ONE transaction under the pipeline-stage lock. order must list every stage id of the pipeline exactly once and may not put a system holding stage first unless it already is; expectedRevision is the order the caller last read (stage ids by position, comma-joined). Owner / admin.
tags:
- Pipelines
parameters:
- $ref: '#/components/parameters/IdParam'
- name: dry_run
in: query
required: false
description: Validate the mutation without persisting changes.
schema:
type: boolean
- name: sandbox
in: query
required: false
description: Run the mutation in sandbox mode.
schema:
type: boolean
requestBody:
required: true
content:
application/json:
schema:
type: object
required:
- order
- expectedRevision
properties:
order:
type: array
items:
type: string
format: uuid
expectedRevision:
type: string
responses:
'200':
description: 'The canonical order and its new revision: { order, revision }'
content:
application/json:
schema:
$ref: '#/components/schemas/DataEnvelope'
'400':
description: Not a complete permutation, or a system holding stage moved first
'409':
description: 'order_changed: the order moved since the caller read it; pipeline_archived: the pipeline was archived while this request waited. Nothing written'
/pipelines/{id}/archive:
post:
operationId: archivePipeline
summary: Archive a pipeline, moving its open deals, in one transaction
description: 'Every open deal moves to the first rep-facing stage of moveOpenDealsTo through the normal transition path (stage-entry rules, automation-only lock, stage-change activities and events, status taxonomy); won / lost deals stay in the pipeline as history; the pipeline is archived (renamed), never deleted, so it can be restored. Any refusal aborts everything: nothing moves, nothing is archived. The default pipeline is refused. Owner / admin.'
tags:
- Pipelines
parameters:
- $ref: '#/components/parameters/IdParam'
- name: dry_run
in: query
required: false
description: Validate the mutation without persisting changes.
schema:
type: boolean
- name: sandbox
in: query
required: false
description: Run the mutation in sandbox mode.
schema:
type: boolean
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
moveOpenDealsTo:
type:
- string
- 'null'
format: uuid
description: The pipeline the open deals move to; null when the pipeline has none
expectedSettingsRevision:
type: string
description: Optional account settings revision the caller read; a mismatch is a 409 stale_settings_revision
responses:
'200':
description: '{ id, archived: true, movedOpenDeals, movedTo }; when another request archived the pipeline first (before or while this one ran): { id, archived: true, alreadyArchived: true, movedOpenDeals: 0, movedTo: null }, nothing moved'
content:
application/json:
schema:
$ref: '#/components/schemas/DataEnvelope'
'400':
description: The target is missing, archived, the pipeline itself, or has no rep-facing stage
'409':
description: 'Refused, nothing changed: open_deals (no target given), target_not_open (the target''s first rep-facing stage is won or lost), stage_requirements (a deal the target stage refuses; the message names it), archive_too_large (more than 500 open deals: move or close them in batches first), dpp_post_close (the DPP feed''s Post-Close pipeline), target_archived (the destination pipeline was archived while this request waited: choose another), intake_target (enabled intake creates deal cards in this pipeline), default_pipeline, stale_settings_revision, archive_raced, or a live unattended-lead sweep on the pipeline'
/pipelines/{id}/restore:
post:
operationId: restorePipeline
summary: Restore an archived pipeline
description: 'Owner or admin. One transaction under the account''s pipeline-order lock: the archived pipeline gets its name back, joins the end of the active order (positions renumbered dense) and is never the default; its stages and closed deals come back with it. Counts toward the plan''s pipeline limit.'
tags:
- Pipelines
parameters:
- $ref: '#/components/parameters/IdParam'
- name: dry_run
in: query
required: false
description: Validate the mutation without persisting changes.
schema:
type: boolean
- name: sandbox
in: query
required: false
description: Run the mutation in sandbox mode.
schema:
type: boolean
responses:
'200':
description: The restored pipeline with its stages
content:
application/json:
schema:
$ref: '#/components/schemas/DataEnvelope'
'403':
description: PLAN_LIMIT_REACHED, or not an owner / admin
'404':
description: No archived pipeline with this id in the account (an active id is a 404 too)
'409':
description: 'name_taken: an active pipeline already has its name (any case). Rename that one first.'
/pipelines/{id}/stages/{stageId}:
patch:
operationId: updatePipelineStage
summary: Update a pipeline stage
tags:
- Pipelines
parameters:
- $ref: '#/components/parameters/IdParam'
- name: stageId
in: path
required: true
schema:
type: string
format: uuid
- name: dry_run
in: query
required: false
description: Validate the mutation without persisting changes.
schema:
type: boolean
- name: sandbox
in: query
required: false
description: Run the mutation in sandbox mode.
schema:
type: boolean
requestBody:
required: true
content:
application/json:
schema:
type: object
required:
- name
properties:
name:
type: string
color:
type: string
sortOrder:
type: integer
isWon:
type: boolean
isLost:
type: boolean
isSystem:
description: Read by the handler from the JSON body (the route declares no schema for its type).
responses:
'200':
description: Stage updated
content:
application/json:
schema:
$ref: '#/components/schemas/DataEnvelope'
'409':
description: 'kind_change_blocked: isWon / isLost would change while open deals occupy the stage (openDeals carries the count); move them first. An empty stage changes freely. pipeline_archived: the pipeline was archived while this request waited; nothing changed'
delete:
operationId: deletePipelineStage
summary: Delete a pipeline stage
description: 'Soft delete: the stage row is kept (so deal history, activities, audit rows and reports keep its name) and the stage disappears from every pipeline and stage list and cannot be moved into; its name may be reused by a new stage. Deleting a stage with no CURRENT deals always works, even if deals once used it (deal history never blocks). Refused (409) only while deals are currently in the stage, unless move_to names another stage of the same pipeline: then the stage''s live deals move there and the stage is deleted in one transaction (the stage only: no status change). Also refused (409) while an active automation uses the stage (an action, trigger, condition or wait-until, or the N-day sweep), while the DPP status map selects it, or while enabled intake creates deal cards in it. Appointment-outcome mappings to the stage and its stage rule are removed, as is its status metadata. A run already parked or in flight before the delete is not tracked: at the step that targets the deleted stage it fails or is skipped with a clear reason in the execution log (recovering a failed run whose frozen graph uses a deleted stage is refused with 409). There is no restore in v1.'
tags:
- Pipelines
parameters:
- $ref: '#/components/parameters/IdParam'
- name: stageId
in: path
required: true
schema:
type: string
format: uuid
- name: dry_run
in: query
required: false
description: Validate the mutation without persisting changes.
schema:
type: boolean
- name: expected_settings_revision
in: query
required: false
description: Query parameter read by this route.
schema:
type: string
- name: expectedSettingsRevision
in: query
required: false
description: Query parameter read by this route.
schema:
type: string
- name: move_to
in: query
required: false
description: Move the stage's live deals to this stage (same pipeline, not a system holding stage, same won / lost setting) and delete the stage.
schema:
type: string
format: uuid
- name: sandbox
in: query
required: false
description: Run the mutation in sandbox mode.
schema:
type: boolean
responses:
'200':
description: 'Stage deleted (expected_settings_revision given): { id, deleted: true, settingsRevision }'
content:
application/json:
schema:
$ref: '#/components/schemas/DataEnvelope'
'204':
description: Stage deleted
'400':
description: move_to is not a stage of the pipeline, is the stage itself or a system holding stage, or has another won / lost setting
'409':
description: Deals are currently in the stage (and no move_to was given), an active unattended-lead automation or the DPP status map selects it, intake_target (enabled intake creates deal cards in this stage), pipeline_archived (the pipeline was archived while this request waited; nothing moved or deleted), or stale_settings_revision
get:
x-generated: true
operationId: getPipelinesIdStagesStageId
summary: Get pipelines id stages stageId
tags:
- Pipelines
parameters:
- name: id
in: path
required: true
schema:
type: string
- name: stageId
in: path
required: true
schema:
type: string
responses:
'200':
description: Success
content:
application/json:
schema:
$ref: '#/components/schemas/DataEnvelope'
components:
schemas:
DataEnvelope:
type: object
properties:
data:
description: Response payload (shape varies by endpoint)
ErrorResponse:
type: object
required:
- error
properties:
error:
type: string
code:
type: string
description: Machine-readable error code (e.g., TRIAL_EXPIRED, PLAN_LIMIT_REACHED)
responses:
NotFound:
description: Resource not found
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
parameters:
IdParam:
name: id
in: path
required: true
schema:
type: string
format: uuid
description: Resource UUID
securitySchemes:
BearerAuth:
type: http
scheme: bearer
description: 'API key authentication. Generate keys in Settings > API Keys.
Keys use the `cdy_` prefix.
Example: `Authorization: Bearer cdy_a1b2c3d4e5f6...`
'
x-mcp:
transport:
- stdio
- http
orchestration: code
endpoint_tools: hidden