> ## Documentation Index
> Fetch the complete documentation index at: https://docs.callkaro.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Assign to Workflow

> Start a normal workflow for one phone number. Optional attributes are saved on the contact first, so every step of the run can use them. The run starts now, or at schedule_at.

<Note>
  Puts one phone number into a [workflow](/workflows/introduction) from your own system, such as a CRM, a website form or your backend. It does the same as assigning a contact from the CRM, and can save details on the contact first so every step of the workflow can use them.
</Note>

## Quick Example

```bash theme={null}
curl -X POST "https://api.callkaro.ai/workflow/assign" \
  -H "Content-Type: application/json" \
  -H "X-API-KEY: YOUR_API_KEY" \
  -d '{
    "to_number": "+919876543210",
    "workflow_id": "6ab6243266ba77dbaf2276c7",
    "attributes": [
      { "key": "first_name", "value": "Rahul", "replace": true }
    ]
  }'
```

## Use Cases

* **New leads**: start a follow-up workflow the moment a form is submitted or a lead is created in your CRM
* **Lifecycle events**: put a customer into a renewal, payment-reminder or onboarding workflow when it happens in your system
* **Scheduled outreach**: pass `schedule_at` to start the workflow at a set time, such as the morning after a sign-up

## Where to find the values

| Field | Where it comes from |
| - | - |
| `X-API-KEY` | **API Key** in the dashboard sidebar. Use the key of the account that owns the workflow. |
| `workflow_id` | Open the workflow in the dashboard. The ID is the last part of the URL. |
| `attributes[].key` | The name of an attribute in **CRM → Attributes**. See [Attributes](/crm/attributes). |

## What happens on your account

<Steps>
  <Step title="The workflow is checked">
    The workflow must belong to the account the API key is for, and must have at least one step. A key from another account gets `404`, the same answer as a workflow that does not exist.
  </Step>

  <Step title="The contact is saved">
    The number becomes a contact in your CRM if it is not one already, and the `attributes` you sent are saved on it.
  </Step>

  <Step title="The run starts">
    The number enters the workflow at **Start**, now or at `schedule_at`. Every step can read the saved attributes: as **Contact** values in conditions, in `{{variables}}`, and in a **Send Call** step's call metadata.
  </Step>
</Steps>

The run then appears with the workflow's other runs, as described in [Enroll and Monitor](/workflows/enroll-and-monitor).

## How attributes are saved

Each row in `attributes` is a `key`, a `value` and an optional `replace`.

* **Matching names**: `key` is matched to your CRM attributes ignoring case, spaces, underscores and hyphens, so `"First Name"` matches `first_name`.
* **`replace: true`** overwrites a value the contact already has.
* **`replace: false`** (the default) only fills an attribute that is empty. An existing value is kept.
* **Types**: the value is checked against the attribute's type. A number attribute needs a number, a date needs a date, and an enum value must be one of its options.
* **Edits made by hand win**: a value someone typed into the CRM is never overwritten through the API, even with `replace: true`.

<Warning>
  Only attributes that already exist in **CRM → Attributes** are saved. A key with no matching attribute is not created; it is listed under `ignored` in the response. Create the attribute first.
</Warning>

The response reports what happened to every row, so a typo does not go unnoticed:

```json theme={null}
"attributes": {
  "updated": ["first_name"],
  "kept_existing": ["city"],
  "ignored": ["frist_name"],
  "invalid": [{ "key": "age", "reason": "not a valid number" }]
}
```

## Scheduling

`schedule_at` is a date and time in **IST**, written as `YYYY-MM-DDTHH:mm:ss` with two digits for the month and day: `2026-12-31T09:30:00`, not `2026-12-31T9:30` or `2026-12-3T09:30:00`.

Leave it out to start now. A time that has already passed also starts now.

## Errors worth handling

<AccordionGroup>
  <Accordion title="404 Workflow not found">
    The workflow ID is wrong, or the API key belongs to a different account from the workflow. Check both.
  </Accordion>

  <Accordion title="409 This number is already running in this workflow">
    The number is partway through this workflow already, so it is not started a second time. The attributes you sent are still saved. The number can be assigned again once its current run has finished.
  </Accordion>

  <Accordion title="400 This workflow has no steps yet / is no longer available">
    Add steps to the workflow and save it, or use a different workflow. Older AI workflows cannot take contacts through this endpoint.
  </Accordion>

  <Accordion title="400 schedule_at must be YYYY-MM-DDTHH:mm:ss (IST)">
    The date is not in the expected shape. Use two digits for every part and a `T` between the date and the time.
  </Accordion>
</AccordionGroup>

<Warning>
  Do not retry a `400`, `404` or `409`: the same request will get the same answer. Only `5xx` responses are worth retrying, and a retry can start the run if the first attempt had not.
</Warning>


## OpenAPI

````yaml POST /workflow/assign
openapi: 3.1.0
info:
  title: CallKaro AI API
  description: Campaign and outbound call management API for CallKaro AI voice agents
  version: 1.0.0
  contact:
    name: CallKaro AI Support
    email: support@callkaro.ai
    url: https://callkaro.ai
servers:
  - url: https://api.callkaro.ai
    description: Production server
security:
  - ApiKeyAuth: []
paths:
  /workflow/assign:
    post:
      tags:
        - Endpoints
      summary: Assign to Workflow
      description: >-
        Start a normal workflow for one phone number. Optional attributes are
        saved on the contact first, so every step of the run can use them. The
        run starts now, or at schedule_at.
      operationId: assignWorkflow
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/WorkflowAssignRequest'
            example:
              to_number: '+919876543210'
              workflow_id: 6ab6243266ba77dbaf2276c7
              attributes:
                - key: first_name
                  value: Rahul
                  replace: true
                - key: city
                  value: Pune
                  replace: false
              schedule_at: '2026-12-31T09:30:00'
      responses:
        '200':
          description: Run started or scheduled
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/WorkflowAssignResponse'
              example:
                status: done
                message: Workflow started
                run_id: wf_run_8c1f2a
                enrollment_id: 3f6c2b1e-7a4d-4c8e-9b1a-2d5e8f0c4a7b
                attributes:
                  updated:
                    - first_name
                  kept_existing:
                    - city
                  ignored: []
                  invalid: []
        '400':
          description: Invalid request, or the workflow cannot take contacts
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                status: error
                message: schedule_at must be YYYY-MM-DDTHH:mm:ss (IST)
        '401':
          description: Missing X-API-KEY
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                status: error
                message: Missing API key
        '404':
          description: No such workflow on the account this key belongs to
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                status: error
                message: Workflow not found
        '409':
          description: This number is already partway through this workflow
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                status: error
                message: This number is already running in this workflow
        '500':
          description: Internal server error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
      security:
        - ApiKeyAuth: []
components:
  schemas:
    WorkflowAssignRequest:
      type: object
      required:
        - to_number
        - workflow_id
      properties:
        to_number:
          type: string
          description: Phone number with country code. A leading + is optional.
          example: '+919876543210'
        workflow_id:
          type: string
          description: >-
            ID of a normal workflow, from the end of its URL when you open it in
            the dashboard.
          example: 6ab6243266ba77dbaf2276c7
        attributes:
          type: array
          maxItems: 50
          description: Values to save on the contact before the run starts.
          items:
            $ref: '#/components/schemas/WorkflowAttribute'
        schedule_at:
          type: string
          description: >-
            When the run should start, as YYYY-MM-DDTHH:mm:ss in IST
            (Asia/Kolkata). Omit it, or pass a time that has passed, to start
            now.
          example: '2026-12-31T09:30:00'
    WorkflowAssignResponse:
      type: object
      properties:
        status:
          type: string
          example: done
        message:
          type: string
          example: Workflow started
        run_id:
          type: string
          description: ID of the run that was started
        enrollment_id:
          type: string
          description: ID shared by every run started in this request
        attributes:
          type: object
          description: >-
            What happened to each attribute you sent. Also returned on 409 and
            500 responses, since the contact is saved before the run starts.
          properties:
            updated:
              type: array
              items:
                type: string
              description: Saved on the contact
            kept_existing:
              type: array
              items:
                type: string
              description: >-
                Not changed: replace was false and the contact already had a
                value
            ignored:
              type: array
              items:
                type: string
              description: 'Not saved: no CRM attribute with this name'
            invalid:
              type: array
              description: 'Not saved: the value did not fit the attribute''s type or options'
              items:
                type: object
                properties:
                  key:
                    type: string
                  reason:
                    type: string
    ErrorResponse:
      type: object
      properties:
        status:
          type: string
          example: error
        message:
          type: string
          description: Error description
          example: 'Missing required field: to_number'
    WorkflowAttribute:
      type: object
      required:
        - key
        - value
      properties:
        key:
          type: string
          description: >-
            Name of a CRM attribute on your account. Case, spaces, underscores
            and hyphens are ignored, so "First Name" matches first_name.
          example: first_name
        value:
          oneOf:
            - type: string
            - type: number
            - type: boolean
          description: >-
            The value to save, at most 1000 characters. It is checked against
            the attribute's type, and an enum value must be one of its options.
          example: Rahul
        replace:
          type: boolean
          default: false
          description: >-
            true overwrites a value the contact already has. false only fills
            the attribute if it is empty.
  securitySchemes:
    ApiKeyAuth:
      type: apiKey
      in: header
      name: X-API-KEY
      description: API key from https://callkaro.ai/dashboard/api-key

````

This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.