# Lead Assignment, Filters and Fields

> Assign Mojeeb leads to teammates, list them by assignee or by custom field, pin them, and add, change or remove the agent's custom lead fields through the Public API.

Once leads arrive, your team routes and sorts them. With the Public API your CRM or automation can do the same: assign each lead to the right teammate, list a teammate's leads or those matching a custom field, pin the ones that need attention, and shape the agent's lead form, which also decides what the AI collects when it saves a lead. Every endpoint lives under one agent: `/v1/agents/{agentId}/…`.

## How do I assign a lead to a teammate?

`POST` the teammate's `user_id` as `assigned_to` to the lead's `assign` endpoint. The teammate becomes the lead's **Owner** in the dashboard, and the lead's conversation is assigned to them too. The teammate must be able to open the agent.

```bash
curl -X POST https://api.mojeeb.app/v1/agents/YOUR_AGENT_ID/leads/LEAD_ID/assign \
  -H "Authorization: Bearer mk_live_YOUR_KEY_HERE" \
  -H "Content-Type: application/json" \
  -d '{ "assigned_to": "TEAMMATE_USER_ID", "reason": "Handles the Riyadh branch." }'
```

Find the `user_id` with [List teammates for an agent](../api-reference/list-agent-members): only teammates whose `can_open_agent` is `true` can be assigned. Anyone else returns `422 assignee_not_allowed`, and nothing changes. `reason` is optional and is kept with the assignment in Mojeeb's records. The response is the lead with its new `assigned_to`. See the [Assign or unassign a lead reference](../api-reference/assign-lead).

## How do I unassign a lead?

`POST` `assigned_to` as `null` to the lead's `assign` endpoint. The lead then shows **No owner** in the dashboard. The field is required, so an empty body is refused instead of unassigning a lead by accident.

```bash
curl -X POST https://api.mojeeb.app/v1/agents/YOUR_AGENT_ID/leads/LEAD_ID/assign \
  -H "Authorization: Bearer mk_live_YOUR_KEY_HERE" \
  -H "Content-Type: application/json" \
  -d '{ "assigned_to": null }'
```

## How do I list a teammate's leads, or the unassigned ones?

Add `assigned_to` to the lead list: a teammate's `user_id`, or `unassigned` for the leads assigned to no one. It combines with the other filters, such as `status`, and a lead is listed only when it matches all of them.

```bash
curl "https://api.mojeeb.app/v1/agents/YOUR_AGENT_ID/leads?assigned_to=unassigned&status=new" \
  -H "Authorization: Bearer mk_live_YOUR_KEY_HERE"
```

`me` isn't taken, because a key isn't a person: it and any other value return `422 invalid_request_body` with `param` `assigned_to`. See the [List leads reference](../api-reference/list-leads).

## How do I filter leads by a custom field?

Pass `custom_fields`: a URL-encoded JSON object that maps a field's key to a clause, an `op` with its `values`. With curl, `-G` and `--data-urlencode` do the encoding for you.

```bash
curl -G https://api.mojeeb.app/v1/agents/YOUR_AGENT_ID/leads \
  -H "Authorization: Bearer mk_live_YOUR_KEY_HERE" \
  --data-urlencode 'custom_fields={"city":{"op":"eq","values":["Cairo"]},"visit_date":{"op":"between","values":["2026-10-01","2026-11-01"]}}'
```

| `op` | `values` | A lead matches when the field |
|---|---|---|
| `eq` | One | Equals the value |
| `in` | One or more | Equals any of the values |
| `ilike` | One | Contains the value, ignoring case; `%` and `_` act as wildcards |
| `between` | Two | Is from the first value, included, to the second, excluded |
| `is_null` | None | Has no value |

Values are compared with the stored value as text. Write dates as `YYYY-MM-DD` so `between` orders them correctly, and don't expect numbers to compare by size. Clauses combine with AND, and a lead that has no value under a key matches only `is_null` there. The number of keys and values per request is limited; see the [List leads reference](../api-reference/list-leads).

## How do I pin or unpin a lead?

`PATCH` the lead with `pinned`: `true` pins it for everyone on the agent, as the dashboard's **Pin** does, and `false` unpins it. Pinned leads come first, on the first page of the lead list.

```bash
curl -X PATCH https://api.mojeeb.app/v1/agents/YOUR_AGENT_ID/leads/LEAD_ID \
  -H "Authorization: Bearer mk_live_YOUR_KEY_HERE" \
  -H "Content-Type: application/json" \
  -d '{ "pinned": true }'
```

Sending the state a lead is already in changes nothing, so a pinned lead keeps its place. Later pages of the list never contain pinned leads, so keep fewer leads pinned than the `limit` you list with. See the [Update a lead reference](../api-reference/update-lead).

## How do I add a lead field?

`POST` a `key`, a `label` and a `type` to the agent's lead fields. The field is added after the agent's other custom fields, in the lead form and as a column of the dashboard's Clients table, and the AI's [Capture Lead](/docs/tools/capture-lead) tool starts collecting it in the background, usually within a minute.

```bash
curl -X POST https://api.mojeeb.app/v1/agents/YOUR_AGENT_ID/lead-fields \
  -H "Authorization: Bearer mk_live_YOUR_KEY_HERE" \
  -H "Content-Type: application/json" \
  -d '{ "key": "preferred_branch", "label": "Preferred branch", "type": "enum", "options": [ { "value": "riyadh", "label": "Riyadh" }, { "value": "jeddah", "label": "Jeddah" } ] }'
```

The label tells the AI what to ask for, so write it the way you would explain the field to a new teammate. `"required": true` makes the AI treat the field as needed before it saves a lead. An `enum` field takes its choices in `options`, and other types take none. The key is where each lead keeps the value in `custom_fields`, and it can't change later.

A key Mojeeb reserves, such as `name`, `status` or `source`, returns `422 invalid_request`. Any other key the agent already has, built-in fields such as `notes` included, returns `409 field_key_taken`, and an agent with no room for another custom field returns `422 lead_field_limit_reached`. The key format, the types and the limits are in the [Add a lead field reference](../api-reference/create-lead-field).

## How do I change a lead field?

`PATCH` a custom field by its key to change its `label`, whether it is `required`, or its `options`. Send only what changes. The key and the type stay; for another type, add a new field. The AI follows the change in the background, usually within a minute.

```bash
curl -X PATCH https://api.mojeeb.app/v1/agents/YOUR_AGENT_ID/lead-fields/preferred_branch \
  -H "Authorization: Bearer mk_live_YOUR_KEY_HERE" \
  -H "Content-Type: application/json" \
  -d '{ "options": [ { "value": "riyadh", "label": "Riyadh" }, { "value": "jeddah", "label": "Jeddah" }, { "value": "dammam", "label": "Dammam" } ] }'
```

`options` replaces every choice, in the order you send them. A choice whose `value` stays keeps what the dashboard set on it, such as its colour, and leads keep values you remove. The key in the path is matched exactly, case included, and a key the agent has no field for returns `404 not_found`. Built-in fields, the lead statuses among them, can't be changed through the API (`422 system_field`). See the [Change a lead field reference](../api-reference/update-lead-field).

## How do I remove a lead field?

`DELETE` a custom field by its key. It leaves the agent's lead form permanently, and the AI stops collecting it in the background, usually within a minute. The response is `204` with no body.

```bash
curl -X DELETE https://api.mojeeb.app/v1/agents/YOUR_AGENT_ID/lead-fields/preferred_branch \
  -H "Authorization: Bearer mk_live_YOUR_KEY_HERE"
```

Leads keep the values they had under the key: the API still returns them in `custom_fields`, the dashboard's table stops showing them, and they show again if you add a field with the same key. Built-in fields can't be removed (`422 system_field`). See the [Delete a lead field reference](../api-reference/delete-lead-field).

## Common questions

### Who is recorded as making an assignment through the API?

The person who created the key. Mojeeb records the assignment as theirs, made by hand, the same way it records them as the author of a key's notes.

### What does `422 assignee_not_allowed` mean?

The teammate can't open the agent: they are limited to other agents, they left your organization, or the id isn't one of your teammates. Choose someone whose `can_open_agent` is `true`, or give them the agent in the dashboard first; see [Team Management](/docs/features/team-management). If Mojeeb couldn't check the teammate's access, the response is `503 access_unavailable` instead: retry with backoff.

### Why does `assigned_to=me` return 422?

A key isn't a person, so it has no leads of its own. Use the `user_id` of the teammate you mean, from [List teammates for an agent](../api-reference/list-agent-members).

### Can I assign a lead to the teammate it already has?

Yes. The request succeeds and the lead stays theirs, so your integration can send an assignment without checking first.

### Why does my custom-field filter return nothing?

Check four things. The key must match the field's key exactly, case included. `eq` and `in` match the stored text exactly, so use `ilike` to ignore case. `between` leaves out its second value, so to include the last day of a month, end at the first day of the next one. And numbers compare as text.

### Can I filter numbers by size?

No. Values compare as text, so `between` doesn't follow numeric order. Use `eq` or `in` with the exact values, or filter numbers in your own system.

### Can I change a field's type or key?

No. Add a new field with the key and type you need. A field you delete and add again under the same key with another type finds the old values as they were, unconverted.

### Can I change the lead statuses through the API?

Not yet. The statuses are the options of the built-in `status` field, and built-in fields can't be changed through the API (`422 system_field`). Edit them in the dashboard with **Edit Statuses**. Moving a lead from one status to another works through the API; see [Contacts, Notes and Leads](./contacts-and-leads).

### Why did creating a field return 409?

The agent already has a field with that key, built-in fields included, and nothing was created. A create you retried after a lost response lands here too: list the lead fields and carry on.

### Why doesn't the AI collect a field I added?

Capture Lead picks up field changes only while it is switched on. If it is off, switch it on from its card on the Tools page, then open **Edit Columns** on the Clients page and save once, as [Capture Lead](/docs/tools/capture-lead) explains.

### Can I pin as many leads as I like?

Yes, there is no cap, but the lead list returns pinned leads on its first page only, in the API and in the dashboard alike. Pin only what needs attention now, and unpin a lead once it is handled.

### Can I delete a lead?

Yes: `DELETE /v1/agents/{agentId}/leads/{leadId}` removes it permanently, with no undo, and a second delete of the same lead returns `404 not_found`. The contact, its notes and the conversation stay. To take a lead out of your team's open work without losing it, move it to a final status instead. See [Contacts, Notes and Leads](./contacts-and-leads).
