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

# POST /v1/identify

> Say who someone is — their customer id, phone or email — and save their traits on the contact.

Says who someone is: your customer id for them, their phone, their email or the website visitor id, with traits. The contact it matches gains any of those identifiers it didn't have, and any trait that is one of your contact fields. With no match, a `phone` creates the contact; without a phone, nothing is created — the ids are remembered, and what they did joins the contact once a phone is known.

Server events need the **OMNI API** and **Customer data** in your plan. They take an OMNI API key — not a Customer data source key (`sk_live_…`), which is now refused.

<Note>Needs the `cdp.events:write` scope.</Note>

## Body

Send at least one identifier.

| Field | Type | Required | Notes |
| :- | :- | :- | :- |
| `user_id` | string | One of these | Your own customer id. `external_id` is the same thing. |
| `phone` | string | One of these | Their mobile number, with its country code. One without is read as your account's country. |
| `email` | string | One of these | Their email address. |
| `anonymous_id` | string | One of these | The website visitor id, to join what they did before logging in. |
| `traits` | object | No | Facts about them. `name` sets the contact's name if it has none; a key that is one of your contact fields is saved there; others are ignored. |
| `consent` | object | No | Marketing opt-ins and stops by channel: `{"whatsapp": true, "sms": false}`. True opts in, false stops. |
| `consent_proof` | string | No | What they agreed to, and where — kept as the proof for `consent`. Up to 500 characters. |

Send an `Idempotency-Key` header to take it once however often the request is retried.

Events are applied in the background, so the answer only says how many were taken. One that can't be applied — no one named, a reserved event name, properties too large — is dropped without stopping the rest, and shows with its reason under **Live events** on **Settings → Data sources** in the panel.

## Request

<CodeGroup>
  ```bash cURL theme={null}
  curl -X POST "https://api.fireflo.au/v1/identify" \
    -H "Authorization: Bearer $OMNI_API_KEY" \
    -H "Content-Type: application/json" \
    -H "Idempotency-Key: identify-CUST-10482-2026-10-06" \
    -d '{
      "user_id": "CUST-10482",
      "phone": "+919876543210",
      "email": "asha@acme.in",
      "traits": {
        "name": "Asha Rao",
        "city": "Pune",
        "loyalty_tier": "gold"
      },
      "consent": {
        "whatsapp": true
      },
      "consent_proof": "Ticked the WhatsApp offers box at checkout on acme.in"
    }'
  ```

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

  import requests

  response = requests.post(
      "https://api.fireflo.au/v1/identify",
      headers={
          "Authorization": f"Bearer {os.environ['OMNI_API_KEY']}",
          "Idempotency-Key": "identify-CUST-10482-2026-10-06",
      },
      json={
          "user_id": "CUST-10482",
          "phone": "+919876543210",
          "email": "asha@acme.in",
          "traits": {
              "name": "Asha Rao",
              "city": "Pune",
              "loyalty_tier": "gold",
          },
          "consent": {
              "whatsapp": True,
          },
          "consent_proof": "Ticked the WhatsApp offers box at checkout on acme.in",
      },
  )
  print(response.status_code, response.json())
  ```

  ```javascript Node theme={null}
  const response = await fetch("https://api.fireflo.au/v1/identify", {
    method: "POST",
    headers: {
      Authorization: `Bearer ${process.env.OMNI_API_KEY}`,
      "Content-Type": "application/json",
      "Idempotency-Key": "identify-CUST-10482-2026-10-06",
    },
    body: JSON.stringify({
      "user_id": "CUST-10482",
      "phone": "+919876543210",
      "email": "asha@acme.in",
      "traits": {
        "name": "Asha Rao",
        "city": "Pune",
        "loyalty_tier": "gold"
      },
      "consent": {
        "whatsapp": true
      },
      "consent_proof": "Ticked the WhatsApp offers box at checkout on acme.in"
    }),
  });
  console.log(response.status, await response.json());
  ```
</CodeGroup>

## Response

`202 Accepted` — taken, to be applied in the background.

```json theme={null}
{
  "accepted": 1
}
```

## Errors

Every refusal is `{"error": {"code", "message", "field"}}`; `field` is there when one input is at fault.

| Status | Error code | When |
| :- | :- | :- |
| 400 | `invalid_request` | The body isn't a JSON object. |
| 413 | `too_large` | The request is over 256 KB. |
| 401 | `key_retired` | The key is a Customer data source key (`sk_live_…`). Those no longer work here: use an OMNI API key with `cdp.events:write` — see [Moving from source keys](/developers/customer-data#moving-from-source-keys). |
| 403 | `scope_missing` | The key doesn't have the `cdp.events:write` scope. |
| 404 | `module_off` | The account doesn't have Customer data on. |

Any request can also be refused for its key, its account or its rate (`key_required`, `invalid_key`, `account_suspended`, `plan_excludes_api`, `address_not_allowed`, `rate_limited`); see [the overview](/api-reference/overview).


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