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

# Capture a Lead

> Create (or merge into) a lead in your Oktivo CRM dashboard.

Push a lead into your Oktivo CRM from any external system — a website form, a
chatbot, an ad platform, a payment flow, or your own backend. The same person
arriving more than once is intelligently de-duplicated into a single lead with a
full source history, so you can fire this endpoint freely without creating
duplicates.

## Authentication

This endpoint accepts an **API Token** (`okt_...`), or a logged-in **JWT**. Send
it as a Bearer token:

```
Authorization: Bearer okt_your_token_here
```

Generate a token in the dashboard under **Settings → API Tokens → Create Token**
(`app.oktivo.com/account`). The `okt_...` value is shown only once — copy it
immediately. Accounts allow up to 5 active tokens. See
[Capturing Leads via API](/guides/lead-capture#1-create-an-api-token) for a
step-by-step walkthrough and [Authentication](/authentication) for rotation and
expiry.

<Note>
  The lead is always scoped to the token owner's account — you cannot create
  leads in another account. The `crm_api` feature must be enabled on your plan.
</Note>

## Identity Rule

A lead must be reachable, so **at least one of `phone` or `email` is required**.
`name` is optional. If you send neither a phone nor an email, the request is
rejected with a `400`.

Phone numbers are accepted in any common format and normalized to E.164. A
10-digit Indian mobile such as `9876543210` becomes `+919876543210`; an
already-E.164 value like `+14155551234` is kept as-is.

## Request Body

<ParamField body="name" type="string">
  Contact's display name. Optional. Maximum 200 characters. Aliases accepted
  (first present wins, in this order): `name`, `contactName`, `contact_name`,
  `fullName`, `full_name`.
</ParamField>

<ParamField body="phone" type="string">
  Phone number. Any format is accepted and normalized to E.164. Required unless
  `email` is provided.
</ParamField>

<ParamField body="email" type="string">
  Email address. Validated when present. Required unless `phone` is provided.
</ParamField>

<ParamField body="status" type="string">
  Pipeline stage for the lead, e.g. `new`, `contacted`, `qualified`,
  `negotiation`, `won`, `lost`. Custom statuses configured for your org are also
  accepted. Defaults to your pipeline's first stage when omitted.
</ParamField>

<ParamField body="leadTemperature" type="string">
  Intent signal. One of `hot`, `warm`, `cold`.
</ParamField>

<ParamField body="opportunityValue" type="number">
  The lead's primary deal amount. Accepts a plain number or a numeric string
  (e.g. `"₹2,50,000"` → `250000`). Feeds the dashboard opportunity funnel.
</ParamField>

<ParamField body="metadata" type="object">
  Acquisition attribution.

  <Expandable title="metadata properties">
    <ParamField body="source" type="string">
      How the lead arrived. Only a known Oktivo source value is honoured;
      anything else falls back to `api`. See [Lead Sources](#lead-sources) below.
    </ParamField>

    <ParamField body="ip" type="string">
      Submitter's IP address. If omitted, it is captured from the request. Used
      as a match signal for ad-platform conversion APIs.
    </ParamField>

    <ParamField body="userAgent" type="string">
      Submitter's User-Agent. If omitted, it is captured from the request.
    </ParamField>

    Any other `metadata` key (for example a platform's verbatim `eventType`) is
    stored as-is on the lead.
  </Expandable>
</ParamField>

<ParamField body="customFields" type="object">
  A bag of custom fields to store on the lead (`industryData` and `fields` are
  accepted aliases). Keys max 50 characters, string values max 2000 characters.
  Maximum **50** custom fields per lead.

  You can also send custom fields as plain top-level keys — any body key that is
  not a reserved field name is treated as a custom field. The explicit
  `customFields` object wins on key collisions.

  Example: `{"course": "Data Science", "campaign": "spring-2026"}`
</ParamField>

<ParamField body="visitorId" type="string">
  Optional web-tracking visitor id. When present, the lead is stitched to that
  visitor's prior anonymous web activity. `visitor_id` is also accepted.
</ParamField>

## Deduplication

Oktivo keeps **one lead per phone (or email) per account**. How a repeat
arrival is handled depends on the source:

* A repeat from a **new source** always merges into the existing lead and
  appends an entry to its source history.
* A repeat from a source the lead **already has** returns a `409` for curated
  sources (`manual`, `csv_import`, `api`, `webhook`) to protect your data from
  accidental overwrites.
* For **self-service / event-stream sources** (`website`, `chatbot`, `landing`,
  `whatsapp`, `google_form`, `ai_powered_lead`, `payment_page_drop`) a repeat
  always merges and the latest non-empty values win — so returning visitors and
  re-fired events never 409.

When a request merges into an existing lead, the response has
`"merged": true` and returns a `200`. A brand-new lead returns `201` with
`"merged": false`.

## Lead Sources

Set `metadata.source` to attribute the lead. Only these canonical values are
honoured; any other value is stored as `api`.

| Source | Meaning |
| - | - |
| `api` | Generic programmatic ingestion (default) |
| `webhook` | Forwarded from an external webhook |
| `website` | Website capture form |
| `chatbot` | Website / app chatbot |
| `whatsapp` | WhatsApp conversation |
| `google_form` | Google Form submission |
| `facebook_ad` / `google_ad` | Ad-platform lead |
| `ai_powered_lead` | Your AI lead-scoring engine |
| `payment_page_drop` | Abandoned checkout |
| `landing` | Hosted landing page demo request |

## Response

<ResponseField name="success" type="boolean">
  `true` when the lead was created or merged.
</ResponseField>

<ResponseField name="merged" type="boolean">
  `true` when the request merged into an existing lead (`200`); `false` for a
  newly created lead (`201`).
</ResponseField>

<ResponseField name="data" type="object">
  <Expandable title="data properties">
    <ResponseField name="leadId" type="string">
      Unique identifier for the lead.
    </ResponseField>

    <ResponseField name="contactUID" type="string">
      Linked contact identifier (used when triggering calls).
    </ResponseField>

    <ResponseField name="name" type="string">Resolved contact name.</ResponseField>
    <ResponseField name="phone" type="string">Normalized E.164 phone.</ResponseField>
    <ResponseField name="email" type="string">Normalized email.</ResponseField>
    <ResponseField name="status" type="string">Pipeline stage.</ResponseField>
    <ResponseField name="leadTemperature" type="string">Lead temperature.</ResponseField>
    <ResponseField name="opportunityValue" type="number">Primary deal amount, or `null`.</ResponseField>
    <ResponseField name="opportunityId" type="string">Created opportunity id, or `null`.</ResponseField>
    <ResponseField name="source" type="string">Resolved canonical source.</ResponseField>
    <ResponseField name="customFields" type="object">Stored custom fields.</ResponseField>
    <ResponseField name="createdAt" type="string">ISO timestamp.</ResponseField>
    <ResponseField name="updatedAt" type="string">ISO timestamp.</ResponseField>
  </Expandable>
</ResponseField>

<RequestExample>
  ```bash cURL theme={null}
  curl -X POST https://client-backend.oktivo.com/api/v1/crm/leads \
    -H "Authorization: Bearer okt_your_token_here" \
    -H "Content-Type: application/json" \
    -d '{
      "name": "Priya Sharma",
      "phone": "9876543210",
      "email": "priya@example.com",
      "status": "new",
      "leadTemperature": "warm",
      "metadata": { "source": "website" },
      "customFields": {
        "course": "Data Science",
        "campaign": "spring-2026"
      }
    }'
  ```

  ```javascript Node.js theme={null}
  const res = await fetch(
    "https://client-backend.oktivo.com/api/v1/crm/leads",
    {
      method: "POST",
      headers: {
        Authorization: `Bearer ${process.env.OKTIVO_API_TOKEN}`,
        "Content-Type": "application/json",
      },
      body: JSON.stringify({
        name: "Priya Sharma",
        phone: "9876543210",
        email: "priya@example.com",
        metadata: { source: "website" },
        customFields: { course: "Data Science" },
      }),
    }
  );
  const data = await res.json();
  ```

  ```python Python theme={null}
  import os, requests

  res = requests.post(
      "https://client-backend.oktivo.com/api/v1/crm/leads",
      headers={
          "Authorization": f"Bearer {os.environ['OKTIVO_API_TOKEN']}",
          "Content-Type": "application/json",
      },
      json={
          "name": "Priya Sharma",
          "phone": "9876543210",
          "email": "priya@example.com",
          "metadata": {"source": "website"},
          "customFields": {"course": "Data Science"},
      },
  )
  print(res.json())
  ```
</RequestExample>

<ResponseExample>
  ```json 201 Created theme={null}
  {
    "success": true,
    "merged": false,
    "data": {
      "leadId": "lead_7f8a9b0c1d2e",
      "contactUID": "contact_3e4f5g6h7i8j",
      "name": "Priya Sharma",
      "phone": "+919876543210",
      "email": "priya@example.com",
      "status": "new",
      "leadTemperature": "warm",
      "opportunityValue": null,
      "opportunityId": null,
      "source": "website",
      "customFields": { "course": "Data Science", "campaign": "spring-2026" },
      "createdAt": "2026-10-06T09:12:33.000Z",
      "updatedAt": "2026-10-06T09:12:33.000Z"
    }
  }
  ```

  ```json 200 Merged theme={null}
  {
    "success": true,
    "merged": true,
    "data": {
      "leadId": "lead_7f8a9b0c1d2e",
      "contactUID": "contact_3e4f5g6h7i8j",
      "name": "Priya Sharma",
      "phone": "+919876543210",
      "email": "priya@example.com",
      "status": "contacted",
      "leadTemperature": "hot",
      "source": "website",
      "customFields": { "course": "Data Science" },
      "createdAt": "2026-10-06T09:12:33.000Z",
      "updatedAt": "2026-10-06T10:40:01.000Z"
    }
  }
  ```

  ```json 400 Validation Error theme={null}
  {
    "success": false,
    "message": "Validation failed",
    "errors": [
      { "field": "phone", "message": "Provide a phone number or an email address" }
    ]
  }
  ```

  ```json 409 Duplicate theme={null}
  {
    "success": false,
    "message": "A lead with this phone already exists for source \"api\"",
    "code": "DUPLICATE_LEAD_SOURCE"
  }
  ```
</ResponseExample>

## Error Codes

| Code | Meaning |
| - | - |
| 400 | Validation failed — check the `errors` array (missing phone/email, bad email, >50 custom fields, oversized value) |
| 401 | Missing or invalid token |
| 403 | `crm_api` feature not enabled on your plan |
| 409 | Duplicate lead for this source (`DUPLICATE_LEAD_SOURCE`) or email already belongs to another lead (`DUPLICATE_LEAD_EMAIL`) |
| 429 | Rate limit exceeded — wait and retry |
| 500 | Internal server error |


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