Conduyt Bulk Operations API

Bulk update, delete, and tag contacts and deals

Operations 8

GET /bulk Get bulk operation status #
POST /bulk/contacts/delete Bulk delete contacts #
POST /bulk/contacts/edit Bulk edit contact fields #
POST /bulk/contacts/tag Bulk add/remove tags on contacts #
POST /bulk/contacts/update Bulk update contacts with field values #
POST /bulk/deals/edit Bulk edit deal fields #
POST /bulk/deals/update Bulk update deals #
GET /bulk-operations/unified List bulk-operations unified #

Work with this as data

Every API here is available over the APIs.io API and to AI agents over MCP.

MCP server

One button, every client — Claude, Cursor, VS Code and the rest.

https://apis.io/mcp

Tools for apis

7 MCP tools reach this
  • find_apisBrowse and filter every API in the catalog.
  • get_api_artifactsOne API's artifacts, grouped by type.
  • get_openapiThe primary OpenAPI for this API.
  • find_similar_apisAPIs that look like this one.
  • apis_io_searchSTART HERE — APIs, providers and tags for one query, each with its total.
  • resolveTurn a domain, URL or GitHub org into the provider it belongs to.
  • find_cohortsEvery scored population of providers in the catalog.
All 92 tools →

Call it yourself

curl for this page
This API
curl "https://apis.io/api/v1/apis/conduyt-com:conduyt-com-bulk-operations-api"
All apis
curl "https://apis.io/api/v1/apis?limit=25"

Discovery needs no key. Ratings and market analysis are Pro.

Get an API key

Free tier, no form to fill in. Signing in shares your email address with us — we store it to create your key and to recognise you if you sign in with another provider. See our Privacy Policy and Terms.

A second provider on the same verified email joins the account you already have.

OpenAPI Specification

conduyt-com-bulk-operations-api-openapi.yml Raw ↑
openapi: 3.2.0
info:
  title: Conduyt Bulk Operations 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: Bulk Operations
  description: Bulk update, delete, and tag contacts and deals
paths:
  /bulk:
    get:
      operationId: getBulkStatus
      summary: Get bulk operation status
      tags:
      - Bulk Operations
      responses:
        '200':
          description: Status of running bulk operations
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: object
                    properties:
                      operations: {}
                    required:
                    - operations
                required:
                - data
      parameters: []
  /bulk/contacts/delete:
    post:
      operationId: bulkDeleteContacts
      summary: Bulk delete contacts
      tags:
      - Bulk Operations
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
              - ids
              properties:
                ids:
                  type: array
                  items:
                    type: string
                    format: uuid
      responses:
        '200':
          description: Contacts deleted
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: object
                    properties:
                      deleted:
                        type: number
                      requested: {}
                      followupsQueued: {}
                    required:
                    - deleted
                    - requested
                required:
                - data
      parameters: []
  /bulk/contacts/edit:
    post:
      operationId: bulkEditContacts
      summary: Bulk edit contact fields
      tags:
      - Bulk Operations
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
              - ids
              - updates
              properties:
                ids:
                  type: array
                  items:
                    type: string
                    format: uuid
                updates:
                  type: object
                  description: The fields to set on every listed contact. A non-empty `country` may be changed; it cannot be cleared (null, empty and whitespace-only values are refused).
                  properties:
                    country:
                      type: string
                      minLength: 1
                      pattern: \S
                      description: A non-empty country. Clearing it is refused with `country_invalid`.
                    addressLine1:
                      description: Read by the handler from the JSON body (the route declares no schema for its type).
                    addressLine2:
                      description: Read by the handler from the JSON body (the route declares no schema for its type).
                    assignedTo:
                      description: Read by the handler from the JSON body (the route declares no schema for its type).
                    city:
                      description: Read by the handler from the JSON body (the route declares no schema for its type).
                    company:
                      description: Read by the handler from the JSON body (the route declares no schema for its type).
                    customFields:
                      description: Read by the handler from the JSON body (the route declares no schema for its type).
                    doNotContact:
                      description: Read by the handler from the JSON body (the route declares no schema for its type).
                    doNotContactReason:
                      description: Read by the handler from the JSON body (the route declares no schema for its type).
                    email:
                      description: Read by the handler from the JSON body (the route declares no schema for its type).
                    firstName:
                      description: Read by the handler from the JSON body (the route declares no schema for its type).
                    jobTitle:
                      description: Read by the handler from the JSON body (the route declares no schema for its type).
                    lastName:
                      description: Read by the handler from the JSON body (the route declares no schema for its type).
                    optedOutEmail:
                      description: Read by the handler from the JSON body (the route declares no schema for its type).
                    optedOutSms:
                      description: Read by the handler from the JSON body (the route declares no schema for its type).
                    phone:
                      description: Read by the handler from the JSON body (the route declares no schema for its type).
                    source:
                      description: Read by the handler from the JSON body (the route declares no schema for its type).
                    state:
                      description: Read by the handler from the JSON body (the route declares no schema for its type).
                    zip:
                      description: Read by the handler from the JSON body (the route declares no schema for its type).
      responses:
        '200':
          description: Contacts updated
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: object
                    properties:
                      updated:
                        type: number
                      requested:
                        type: number
                required:
                - data
        '400':
          description: Nothing was written. `code` is `country_invalid` ("country cannot be cleared") when `updates.country` is null, empty or whitespace-only; `phone_invalid` when a phone or phone-type custom field is not a valid number for a row's country; other 400s describe the invalid request in `error`.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
      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
  /bulk/contacts/tag:
    post:
      operationId: bulkTagContacts
      summary: Bulk add/remove tags on contacts
      tags:
      - Bulk Operations
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                contactIds:
                  description: Read by the handler from the JSON body (the route declares no schema for its type).
                tagId:
                  description: Read by the handler from the JSON body (the route declares no schema for its type).
      responses:
        '200':
          description: Tags updated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/DataEnvelope'
      parameters:
      - name: Idempotency-Key
        in: header
        required: false
        description: 'Optional: one key per bulk tag operation (1-255 printable ASCII characters, not blank), reused only to retry the SAME request. Same key + same request body replays the original response (Idempotent-Replay: true); same key + a different body is refused with 422 code idempotency_request_conflict (the key is bound to the first request for good: verify it, then use a NEW key). Two DISTINCT 409s under this key, never interchangeable: (1) the ORIGINAL attempt is still being processed (code idempotency_in_progress) — wait for the Retry-After seconds given, then retry with the SAME key; (2) the key was already SPENT by a prior attempt that failed or has an unknown outcome (code idempotency_key_spent) — verify whether that attempt took effect BEFORE sending a brand-new key; resending the SAME key here only replays this 409 again, it never re-runs the request. A third, rare 409 (code idempotency_conflict_retry) means a concurrent claim of this key could not be resolved and nothing was run or stored: retry with the SAME key. If the wrapper''s OWN write to RELEASE a failed attempt''s claim cannot be confirmed, the SAME key instead answers 503 idempotency_claim_held until the held claim expires — retry with a NEW key, never the same one. If the wrapper''s OWN write to confirm a SUCCESSFUL completion cannot be confirmed, the mutation may have gone through anyway: the SAME key instead answers 503 idempotency_outcome_unknown — verify the outcome before any new-key retry, exactly as case (2) above. A blank or malformed key is refused with 400 idempotency_key_invalid, on dry_run requests too (a dry run never records the key).'
        schema:
          type: string
          pattern: ^[!-~](?:[ -~]{0,253}[!-~])?$
  /bulk/contacts/update:
    post:
      operationId: bulkUpdateContacts
      summary: Bulk update contacts with field values
      tags:
      - Bulk Operations
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                updates:
                  type: array
                  items:
                    type: object
                    properties:
                      id:
                        description: Read by the handler from the JSON body (the route declares no schema for its type).
                    description: Read by the handler from the JSON body (the route declares no schema for its type).
                  description: Read by the handler from the JSON body (the route declares no schema for its type).
      responses:
        '200':
          description: Contacts updated
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: object
                    properties:
                      summary: {}
                      results: {}
                    required:
                    - summary
                    - results
                required:
                - data
      parameters: []
  /bulk/deals/edit:
    post:
      operationId: bulkEditDeals
      summary: Bulk edit deal fields
      tags:
      - Bulk Operations
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
              - ids
              - updates
              properties:
                ids:
                  type: array
                  items:
                    type: string
                    format: uuid
                updates:
                  type: object
                  properties:
                    assignedTo:
                      description: Read by the handler from the JSON body (the route declares no schema for its type).
                    currency:
                      description: Read by the handler from the JSON body (the route declares no schema for its type).
                    expectedCloseDate:
                      description: Read by the handler from the JSON body (the route declares no schema for its type).
                    lostReason:
                      description: Read by the handler from the JSON body (the route declares no schema for its type).
                    priority:
                      description: Read by the handler from the JSON body (the route declares no schema for its type).
                    stageId:
                      description: Read by the handler from the JSON body (the route declares no schema for its type).
                    status:
                      description: Read by the handler from the JSON body (the route declares no schema for its type).
                    title:
                      description: Read by the handler from the JSON body (the route declares no schema for its type).
                    value:
                      description: Read by the handler from the JSON body (the route declares no schema for its type).
      responses:
        '200':
          description: Deals updated
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: object
                    properties:
                      updated: {}
                      skippedNoOp: {}
                      requested: {}
                      errors: {}
                    required:
                    - updated
                    - skippedNoOp
                    - requested
                    - errors
                required:
                - data
      parameters: []
  /bulk/deals/update:
    post:
      operationId: bulkUpdateDeals
      summary: Bulk update deals
      description: 'Updates up to 100 deals in one request. Each item in `updates` carries the deal `id` plus the fields to change for THAT deal, and each is applied on its own: one item failing never blocks the others. A valid batch of 1 to 100 items answers 200 with a per-item outcome for every item; authentication, billing, request-shape, rate-limit and server failures use their documented top-level statuses instead. Rate limited to 5 requests per minute per account and IP. Length limits count UTF-16 code units as JavaScript does, so a character outside the Basic Multilingual Plane such as an emoji counts 2; JSON Schema counts it 1, so a string near the limit can pass this schema and still be refused per item.'
      tags:
      - Bulk Operations
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
              - updates
              properties:
                updates:
                  type: array
                  minItems: 1
                  maxItems: 100
                  description: One entry per deal. A body without a non-empty `updates` array answers 400; more than 100 entries answers 422.
                  items:
                    type: object
                    required:
                    - id
                    description: The deal `id` plus any of the editable fields below. Any other key is ignored, not applied. An item with no editable field fails with "No valid fields to update".
                    properties:
                      id:
                        type: string
                        format: uuid
                        description: The deal to update. A deal that does not exist, or that the caller cannot edit, fails that item with \"Deal not found\".
                      title:
                        type: string
                        minLength: 1
                        maxLength: 200
                        description: Max 200 UTF-16 code units (an emoji counts 2).
                      stageId:
                        type: string
                        format: uuid
                        pattern: ^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$
                        description: Must be a stage of the deal's pipeline; stage-entry rules (required fields, lost reason) apply per item.
                      value:
                        description: A number or a plain decimal string (digits with an optional fraction, no exponent); null clears it. After rounding half away from zero to 2 decimal places the value must lie between -9,999,999,999.99 and 9,999,999,999.99, so 9999999999.994 is accepted and 9999999999.995 is not.
                        oneOf:
                        - type: number
                          exclusiveMinimum: -9999999999.995
                          exclusiveMaximum: 9999999999.995
                        - type: string
                          pattern: ^-?0*(?!9999999999\.99[5-9])\d{1,10}(\.\d+)?$
                        - type: 'null'
                      currency:
                        type: string
                        maxLength: 10
                        description: Max 10 UTF-16 code units (an emoji counts 2).
                      status:
                        type: string
                        enum:
                        - open
                        - won
                        - lost
                      priority:
                        type: string
                        maxLength: 30
                        description: Max 30 UTF-16 code units (an emoji counts 2).
                      assignedTo:
                        type:
                        - string
                        - 'null'
                        format: uuid
                        pattern: ^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$
                      expectedCloseDate:
                        type:
                        - string
                        - 'null'
                        maxLength: 50
                        pattern: ^\d{4}-\d{2}-\d{2}(?:[T ]\d{2}:\d{2}(?::\d{2}(?:\.\d{1,9})?)?(?:Z|[+-]\d{2}:?\d{2})?)?$
                        description: ISO 8601 date (YYYY-MM-DD) or date-time (YYYY-MM-DDTHH:MM[:SS[.fff]][Z|+HH:MM], a space may replace the T). The pattern checks the shape only; the route also rejects impossible calendar or clock values (2026-02-31, month 13, hour 24) with a per-item error, which a JSON schema cannot express. null clears it.
                      lostReason:
                        type:
                        - string
                        - 'null'
                        maxLength: 500
                        description: Max 500 UTF-16 code units (an emoji counts 2); null clears it.
                      customFields:
                        type: object
                        additionalProperties: true
                        description: Merged into the deal's existing custom fields (keys you send replace those keys; others stay). The merged map, as stored, may not exceed 65,536 bytes.
            example:
              updates:
              - id: 5f0c2b1e-8a47-4c1d-9e3a-2b7d6c4a1f90
                stageId: 7d3e9a52-1c64-4b8f-a0d2-3e5f8b6c9a11
                priority: high
              - id: a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d
                value: 12500
                expectedCloseDate: 2026-11-30
      responses:
        '200':
          description: 'Per-item results: each update succeeds or fails on its own (success false with an error). An item whose merged customFields map, as stored (PostgreSQL''s jsonb form, where numbers like 1e300 expand to their full digits), would exceed 65,536 bytes fails with the error "customFields exceeds maximum size of 65536 bytes" and is not written.'
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                properties:
                  data:
                    type: object
                    required:
                    - summary
                    - results
                    properties:
                      summary:
                        type: object
                        required:
                        - total
                        - succeeded
                        - failed
                        - skippedNoOp
                        - written
                        properties:
                          total:
                            type: integer
                            description: Items in the request.
                          succeeded:
                            type: integer
                            description: Items whose desired state holds, including skippedNoOp items.
                          failed:
                            type: integer
                          skippedNoOp:
                            type: integer
                            description: Succeeded items that changed nothing because the deal already held the requested state.
                          written:
                            type: integer
                            description: Items that actually changed a deal (succeeded minus skippedNoOp).
                      results:
                        type: array
                        description: One entry per request item, in request order.
                        items:
                          type: object
                          required:
                          - id
                          - success
                          properties:
                            id:
                              type: string
                              description: The item's id, or \"unknown\" when the item had none.
                            success:
                              type: boolean
                            skippedNoOp:
                              type: boolean
                              description: Present and true on a success that changed nothing.
                            error:
                              type: string
                              description: Present when success is false, for example \"Deal not found\", \"Missing id\", \"No valid fields to update\", a validation message, or \"Deal changed concurrently — refresh and retry\". There is no per-item error code.
              example:
                data:
                  summary:
                    total: 3
                    succeeded: 2
                    failed: 1
                    skippedNoOp: 0
                    written: 2
                  results:
                  - id: 5f0c2b1e-8a47-4c1d-9e3a-2b7d6c4a1f90
                    success: true
                  - id: a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d
                    success: true
                  - id: 0d9c8b7a-6f5e-4d3c-8b2a-1f0e9d8c7b6a
                    success: false
                    error: Deal not found
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '402':
          description: The account's trial or subscription is not active.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '403':
          $ref: '#/components/responses/Forbidden'
        '422':
          description: More than 100 entries in `updates` (\"Maximum 100 updates per request\"). Nothing was written.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '429':
          $ref: '#/components/responses/RateLimited'
        '500':
          description: Unexpected failure outside the per-item loop, for example a request body that is not valid JSON. Nothing is reported per item.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
      parameters: []
  /bulk-operations/unified:
    get:
      x-generated: true
      operationId: getBulkOperationsUnified
      summary: List bulk-operations unified
      tags:
      - Bulk Operations
      parameters:
      - name: dateFrom
        in: query
        required: false
        description: Query parameter read by this route.
        schema:
          type: string
      - name: dateTo
        in: query
        required: false
        description: Query parameter read by this route.
        schema:
          type: string
      - name: objectType
        in: query
        required: false
        description: Query parameter read by this route.
        schema:
          type: string
      - name: page
        in: query
        required: false
        description: Query parameter read by this route.
        schema:
          type: integer
          default: 1
          minimum: 1
        x-default-provenance: handler_literal
      - name: per_page
        in: query
        required: false
        description: Query parameter read by this route.
        schema:
          type: integer
          default: 20
          minimum: 1
          maximum: 100
        x-default-provenance: handler_literal
      - name: search
        in: query
        required: false
        description: Query parameter read by this route.
        schema:
          type: string
      - name: status
        in: query
        required: false
        description: Query parameter read by this route.
        schema:
          type: string
      - name: type
        in: query
        required: false
        description: Query parameter read by this route.
        schema:
          type: string
      - name: userId
        in: query
        required: false
        description: Query parameter read by this route.
        schema:
          type: string
      responses:
        '200':
          description: Success
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: object
                    properties:
                      data: {}
                      meta: {}
                    required:
                    - data
                    - meta
                required:
                - data
components:
  responses:
    Forbidden:
      description: Insufficient permissions
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
    Unauthorized:
      description: Authentication required
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
    RateLimited:
      description: Too many requests
      headers:
        Retry-After:
          schema:
            type: integer
          description: Seconds until next allowed request
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
    BadRequest:
      description: Bad request — validation error
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
  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)
  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