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

# Create Contact

> Add a new contact to your workspace

## Request

### Headers

```
Authorization: Bearer wbk_your_api_key_here
Content-Type: application/json
```

### Body Parameters

<Info>
  **Name handling**: Send any of `full_name`, `first_name` + `last_name`, or just an `email`. The API splits / composes / derives the missing pieces. It also understands the `"Last, First"` format.
</Info>

<ParamField body="full_name" type="string">
  Contact's full name. If only this is given, the API splits on the last whitespace (e.g. `"Hans Müller"` → first=`Hans`, last=`Müller`). Accepts the `"Last, First"` format (e.g. `"Müller, Hans"`).
</ParamField>

<ParamField body="first_name" type="string">
  Contact's first name. Composed into `full_name` if `full_name` isn't provided.
</ParamField>

<ParamField body="last_name" type="string">
  Contact's last name.
</ParamField>

<ParamField body="email" type="string">
  Email address. Used for duplicate detection and, as a last resort, to derive a name (e.g. `hans.mueller@x.de` → first=`Hans`, last=`Mueller`) when no name is given.
</ParamField>

<ParamField body="phone" type="string">
  Phone number with country code
</ParamField>

<ParamField body="job_title" type="string">
  Job title or position
</ParamField>

<ParamField body="company" type="string">
  Company or organization name
</ParamField>

<ParamField body="related_company_id" type="string">
  UUID of an existing Company to link this contact to. Also accepts the legacy `company_id` key.
</ParamField>

<ParamField body="linkedin_url" type="string">
  LinkedIn profile URL
</ParamField>

<ParamField body="priority" type="string | number">
  Either `"high"` / `"medium"` / `"low"` or an integer (1/2/3).
</ParamField>

<ParamField body="tags" type="array">
  Array of tag strings for categorization
</ParamField>

<ParamField body="custom_fields" type="object">
  Arbitrary JSON object preserved alongside the contact — ideal for domain-specific columns from imported CSVs (e.g. matter numbers, jurisdiction codes).
</ParamField>

<ParamField body="created_date" type="string">
  Optional ISO 8601 timestamp. Honored verbatim if it's within the last 5 years and no more than 24 h in the future; otherwise the server `now()` is used. Never null.
</ParamField>

## Response

<ResponseField name="data" type="object">
  The created contact object.

  <Expandable title="Contact object">
    <ResponseField name="id" type="string">Unique contact ID (UUID)</ResponseField>
    <ResponseField name="first_name" type="string">Derived or provided first name</ResponseField>
    <ResponseField name="last_name" type="string">Derived or provided last name</ResponseField>
    <ResponseField name="full_name" type="string">Derived or provided full name</ResponseField>
    <ResponseField name="email" type="string">Email address</ResponseField>
    <ResponseField name="priority" type="number">Priority (1=high, 2=medium, 3=low)</ResponseField>
    <ResponseField name="created_date" type="string">ISO 8601 timestamp</ResponseField>
  </Expandable>
</ResponseField>

<CodeGroup>
  ```bash cURL theme={null}
  curl -X POST \
    https://data.leadlex.com/functions/v1/api-gateway/v1/contacts \
    -H "Authorization: Bearer wbk_your_api_key_here" \
    -H "Content-Type: application/json" \
    -d '{
      "full_name": "Müller, Hans",
      "email": "hans@roche.com",
      "job_title": "IP Partner",
      "company": "Roche AG"
    }'
  ```

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

  API_KEY = "wbk_your_api_key_here"
  BASE_URL = "https://data.leadlex.com/functions/v1/api-gateway"

  headers = {
      "Authorization": f"Bearer {API_KEY}",
      "Content-Type": "application/json"
  }

  # Any of these shapes works — the API fills in the rest
  data = { "full_name": "Müller, Hans", "email": "hans@roche.com" }
  # or
  # data = { "first_name": "Hans", "last_name": "Müller", "email": "hans@roche.com" }
  # or
  # data = { "email": "hans.mueller@roche.com" }   # name derived from local part

  response = requests.post(f"{BASE_URL}/v1/contacts", headers=headers, json=data)
  contact = response.json()["data"]
  print(contact["first_name"], contact["last_name"], contact["full_name"])
  # → Hans Müller Hans Müller
  ```

  ```javascript JavaScript theme={null}
  const response = await fetch(
    'https://data.leadlex.com/functions/v1/api-gateway/v1/contacts',
    {
      method: 'POST',
      headers: {
        'Authorization': 'Bearer wbk_your_api_key_here',
        'Content-Type': 'application/json'
      },
      body: JSON.stringify({
        full_name: 'Müller, Hans',
        email: 'hans@roche.com',
        job_title: 'IP Partner'
      })
    }
  );

  const { data } = await response.json();
  console.log(`Created contact: ${data.first_name} ${data.last_name}`);
  ```
</CodeGroup>

### Example Response

```json theme={null}
{
  "data": {
    "id": "789e0123-e45b-67c8-a901-234567890abc",
    "first_name": "Hans",
    "last_name": "Müller",
    "full_name": "Hans Müller",
    "email": "hans@roche.com",
    "priority": null,
    "created_date": "2026-04-21T14:30:00.000Z"
  }
}
```

## Validation

<Warning>
  At least one of `first_name`, `last_name`, `full_name`, or `email` must be provided. If none are present the API returns `validation_error`.
</Warning>

### Name derivation rules

Applied in order; the first rule that yields a name wins:

1. **`"Last, First"` format** — if `full_name` contains a comma and first/last are empty, it's split into last / first.
2. **Split `full_name`** — when `full_name` is given but `first_name` / `last_name` are empty, it's split on the last whitespace. "Jean-Claude van Damme" → first=`Jean-Claude van`, last=`Damme`.
3. **Compose `full_name`** — when `first_name` / `last_name` are given but `full_name` is empty.
4. **Derive from email** — local part is split on `._-+` into Title Case tokens.

### Duplicate Detection

The API checks for duplicates by:

* Exact email match (case-insensitive)
* Full-name ILIKE match

If a duplicate is found the Lexi agent prompts for confirmation. The REST endpoint still creates the contact (pass `force_create: false` on the agent tool to suppress).

## Errors

| Status | Code                       | Description                         |
| ------ | -------------------------- | ----------------------------------- |
| 400    | `validation_error`         | No name or email provided           |
| 401    | `invalid_key`              | Invalid API key                     |
| 403    | `insufficient_permissions` | Missing `contacts:write` permission |
| 429    | `rate_limited`             | Rate limit exceeded                 |

### Example Validation Error

```json theme={null}
{
  "error": {
    "code": "validation_error",
    "message": "A name (first_name, last_name, or full_name) or email is required"
  }
}
```
