# Queues

> List, preview, create, change and delete queues through the Fondaro API: saved views with lanes that say who to call next and why.

A queue is a saved view with lanes. `root` says who is in it, each lane says which of those leads it takes and in what order, and `rest` says what happens to everyone else. A lead is in the first lane it matches. The product guide is [Queues](https://www.fondaro.com/docs/dashboard/crm/queues.md); this page lists the `/v1` routes.

Reading needs `crm:read`. Writing needs `crm:write` and an active plan. No role is needed: a queue is your own saved view, so a key acts on the queues of the person it belongs to.

- You change or delete only queues you made. Another person's queue answers `403` with `QUEUE_NOT_YOURS`, and the queue Fondaro makes for everyone, My day, answers `403` with `QUEUE_IS_SYSTEM`.
- A team queue (`calling.pool` is `team`) must be `shared` and only an organization admin can set one: `QUEUE_TEAM_POOL_ADMIN_ONLY` (`403`) and `QUEUE_TEAM_POOL_NOT_SHARED` (`400`).
- Queues are also available to AI clients through [Fondaro MCP](https://www.fondaro.com/docs/api/mcp.md), with the same rules.

## List queues

`GET /v1/queues`

The queues you can open: your own, shared ones and My day. Each comes back in plain words and as its full definition.

```bash
curl https://api.fondaro.com/v1/queues \
  -H "Authorization: Bearer $FONDARO_API_KEY"
```

```json
{
  "queues": [
    {
      "id": "5c1d9f2e-0000-4000-8000-000000000001",
      "name": "Morning calls",
      "icon": "Sun",
      "scope": "private",
      "mine": true,
      "system": false,
      "summary": "Your leads. Overdue first, then new leads.",
      "description": {
        "who": "Leads you own",
        "lanes": ["Overdue: task past due, oldest due first", "New: status New, newest first"],
        "rest": "Everyone else is not listed",
        "order": "",
        "calling": "Each person is given the leads they own. A lead rests 2 hours after a call.",
        "rules": []
      },
      "root": { "type": "group", "combinator": "and", "children": [] },
      "lanes": [],
      "rest": "hide",
      "sort": null,
      "calling": { "pool": "owners", "coolDownMinutes": 120 },
      "rules": [],
      "updatedAt": "2026-10-10T08:12:44.000Z"
    }
  ]
}
```

`mine` is true when you made the queue. `system` is true for the queue Fondaro made for everyone. The `description` text is built by Fondaro and can change, so branch on `root`, `lanes` and the other fields, not on the words.

## Get a queue

`GET /v1/queues/{queueId}`

One queue, in the same shape as an item of the list.

```bash
curl https://api.fondaro.com/v1/queues/5c1d9f2e-0000-4000-8000-000000000001 \
  -H "Authorization: Bearer $FONDARO_API_KEY"
```

A queue you cannot open answers `404`.

## Preview a queue

`POST /v1/queues/preview`

Try a queue without saving anything. Send a saved `queueId`, or an unsaved `definition`; one of the two is required. Needs `crm:read`.

| Parameter | In | Type | Required | Description |
|-----------|----|------|----------|-------------|
| `queueId` | Body | string (uuid) | One of the two | A saved queue to try |
| `definition` | Body | object | One of the two | An unsaved queue: any of `root`, `lanes`, `rest`, `sort`, `calling`, `rules`, as in Create a queue |

The answer has the size of each lane, how many leads fit no lane, how many leads would be offered each change now, and the first ten leads of each lane with why each is there.

```bash
curl -X POST https://api.fondaro.com/v1/queues/preview \
  -H "Authorization: Bearer $FONDARO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "definition": {
      "root": {
        "type": "group", "combinator": "and",
        "children": [
          { "type": "condition", "field": "assignee", "operator": "has_any_of",
            "value": { "kind": "strings", "values": ["$me"] } }
        ]
      },
      "lanes": [
        {
          "name": "Overdue",
          "when": {
            "type": "group", "combinator": "and",
            "children": [
              { "type": "condition", "field": "taskOverdue", "operator": "is_true",
                "value": { "kind": "boolean", "value": true } }
            ]
          },
          "sort": { "key": "nextTaskDueAt", "dir": "asc" }
        }
      ],
      "rest": "hide"
    }
  }'
```

```json
{
  "summary": "Your leads. Overdue first.",
  "description": { "who": "Leads you own", "lanes": ["Overdue: task past due, oldest due first"], "rest": "Everyone else is not listed", "order": "", "calling": "Each person is given the leads they own. A lead rests 2 hours after a call.", "rules": [] },
  "lanes": [{ "id": "overdue", "name": "Overdue", "count": 4 }],
  "restCount": 31,
  "offers": [],
  "rows": [
    {
      "id": 18204,
      "firstName": "Ana",
      "lastName": "Torres",
      "laneId": "overdue",
      "laneName": "Overdue",
      "reason": "Task 2 days overdue"
    }
  ],
  "rowsPerLane": 10
}
```

`$me` is read as the person the key belongs to. `restCount` counts the leads in the queue that no lane takes, whether or not `rest` lists them. Day boundaries such as "due today" use UTC here.

## Create a queue

`POST /v1/queues`

Needs `crm:write` and an active plan. Accepts an `Idempotency-Key` header; see [Errors and limits](https://www.fondaro.com/docs/api/errors-and-limits.md).

| Parameter | In | Type | Required | Description |
|-----------|----|------|----------|-------------|
| `name` | Body | string | Yes | Up to 40 characters. Shown in the sidebar |
| `icon` | Body | string | No | A sidebar icon name such as `Sun` or `House` |
| `scope` | Body | string | No | `private` (default) or `shared` with the whole organization |
| `root` | Body | group | No | Who is in the queue at all. Leave out for every lead the key's person can see |
| `lanes` | Body | array | No | Up to 8 lanes, worked in order. Without lanes it is a plain list ordered by `sort` |
| `rest` | Body | string | No | `hide` or `last` (default). What happens to leads that fit no lane |
| `sort` | Body | object | No | The order of a queue with no lanes |
| `calling` | Body | object | No | Who gets the next lead when someone presses Dial |
| `rules` | Body | array | No | Up to 10 offers, see Offers below |

### Filters: `root` and a lane's `when`

A group has a `combinator` (`and` or `or`) and `children`: conditions, or one more level of group. Up to 25 children per group.

A condition is `{ type: "condition", field, operator, value }`:

| Field | Operators | Value |
|-------|-----------|-------|
| `assignee` | `has_any_of`, `has_all_of`, `has_none_of`, `is_unassigned` | `{ kind: "strings", values }`: user ids, or `$me` |
| `tag` | `has_any_of`, `has_all_of`, `has_none_of` | `{ kind: "strings", values }`: tag ids |
| `crmStatus` | `has_any_of`, `has_none_of` | `{ kind: "strings", values }`: `lead` (New), `potential`, `bad_timing`, `client`, `unqualified` |
| `source`, `leadType`, `preferredLanguage` | `has_any_of`, `has_none_of` | `{ kind: "strings", values }` |
| `phone`, `email` | `is_present`, `is_missing` | `{ kind: "empty" }` |
| `createdAt`, `lastCallAt`, `lastEmailAt`, `lastTouchAt`, `lastInboundAt` | `in_last_n_days`, `not_in_last_n_days`, `in_last_n_hours`, `not_in_last_n_hours`, `is_on`, `is_before`, `is_after`, `is_between`, `is_never` | `{ kind: "integer", value }` for last N days or hours; a date value for the rest; `{ kind: "empty" }` for `is_never` |
| `nextTaskDueAt` | `in_next_n_days` (0 is today), `is_before`, `is_after`, `is_between`, `is_on`, `is_never` | as dates |
| `taskOverdue` | `is_true`, `is_false` | `{ kind: "boolean", value }` |
| `lastCallStatus` | `has_any_of`, `has_none_of` | `{ kind: "strings", values }`: `completed`, `no-answer`, `busy`, `failed`, `canceled` |
| `lastCallDirection` | `has_any_of`, `has_none_of` | `{ kind: "strings", values }`: `inbound`, `outbound` |
| `unansweredStreak` | `eq`, `lt`, `gt`, `between` | `{ kind: "integer", value }`, or `integerRange` with `min` and `max`: outbound calls in a row with no answer |
| `callCount` | `eq`, `lt`, `gt`, `between` | as `unansweredStreak` |
| `callSignal` | `has_any_of`, `has_none_of` | `{ kind: "strings", values }`: what the last call said, such as `call.callback_promised` or `call.not_interested` |
| `notesContain` | `contains` | `{ kind: "string", value }` |
| `hasIncompleteTask`, `hasOpenDeal` | `is_true`, `is_false` | `{ kind: "boolean", value }` |
| `dealStage` | `has_any_of`, `has_none_of` | `{ kind: "strings", values }` |

`$me` is read as the person who opens the queue, so the same queue is personal to each of them. Read without a person, it is refused with `400`.

### Lanes

| Field | Type | Description |
|-------|------|-------------|
| `id` | string | Optional. A stable id of up to 40 characters; made from the name when left out |
| `name` | string | Up to 40 characters. A name, not a sentence |
| `when` | group | Who goes in this lane. A lead is in the first lane it matches; an empty group takes everyone not caught by an earlier lane |
| `sort` | object | `{ key, dir }`: the order inside the lane |

`sort.key` is one of `purchasedAt` (when the lead arrived), `nextTaskDueAt`, `lastCallAt`, `lastTouchAt`, `lastInboundAt`, `totalCalls`, `pendingTasks`, `firstName` or `crmStatus`. `dir` is `asc` or `desc`.

### Calling

| Field | Type | Description |
|-------|------|-------------|
| `pool` | string | `owners` (default): each person is given the leads they own. `team`: anyone in the pool is given the next lead, and the first connected call claims an unassigned one. A team queue must be `shared`, and only an admin sets it |
| `people` | object | For `team`: `{ userIds, teamIds }`. Leave out for everyone who can open the queue |
| `coolDownMinutes` | integer | 0 to 1440. How long a lead rests after any call to it. Default 120 |

### Offers

An offer is how a queue proposes a change after a call, a finished task or the passing of time. It never changes a lead by itself: the person sees one plain sentence and taps it, with Undo after. See [Queues](https://www.fondaro.com/docs/dashboard/crm/queues.md#take-the-offer-after-a-call).

| Field | Type | Description |
|-------|------|-------------|
| `id` | string | Optional. A stable id; made for you when left out |
| `when` | string | `call_ended`, `task_completed` or `time_passed` |
| `if` | group | Who it is about, with the same filters as a lane. An empty group offers to every lead in the queue |
| `offer` | object | One change: `{ kind: "set_status", status }`, `{ kind: "add_tag", tagId }`, `{ kind: "remove_tag", tagId }`, `{ kind: "assign", userId }` or `{ kind: "follow_up", inDays }` (0 to 365; a call back task) |

```bash
curl -X POST https://api.fondaro.com/v1/queues \
  -H "Authorization: Bearer $FONDARO_API_KEY" \
  -H "Idempotency-Key: 0b6f8f0e-6d2b-4b8a-9f43-5f1c2a7d9e11" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Morning calls",
    "icon": "Sun",
    "root": {
      "type": "group", "combinator": "and",
      "children": [
        { "type": "condition", "field": "assignee", "operator": "has_any_of",
          "value": { "kind": "strings", "values": ["$me"] } }
      ]
    },
    "lanes": [
      {
        "name": "Overdue",
        "when": {
          "type": "group", "combinator": "and",
          "children": [
            { "type": "condition", "field": "taskOverdue", "operator": "is_true",
              "value": { "kind": "boolean", "value": true } }
          ]
        },
        "sort": { "key": "nextTaskDueAt", "dir": "asc" }
      },
      {
        "name": "Chasing",
        "when": {
          "type": "group", "combinator": "and",
          "children": [
            { "type": "condition", "field": "unansweredStreak", "operator": "between",
              "value": { "kind": "integerRange", "min": 1, "max": 9 } },
            { "type": "condition", "field": "lastCallAt", "operator": "not_in_last_n_days",
              "value": { "kind": "integer", "value": 2 } }
          ]
        },
        "sort": { "key": "lastCallAt", "dir": "asc" }
      }
    ],
    "rest": "hide",
    "calling": { "pool": "owners", "coolDownMinutes": 120 },
    "rules": [
      {
        "when": "call_ended",
        "if": {
          "type": "group", "combinator": "and",
          "children": [
            { "type": "condition", "field": "unansweredStreak", "operator": "gt",
              "value": { "kind": "integer", "value": 9 } }
          ]
        },
        "offer": { "kind": "set_status", "status": "unqualified" }
      }
    ]
  }'
```

The answer is `201` with the queue, in the shape of an item of the list. The queue is private unless you set `scope` to `shared`. It appears in the sidebar of the person the key belongs to the next time they open Fondaro.

## Update a queue

`PATCH /v1/queues/{queueId}`

Change a queue you made. Send only what changes; fields you leave out stay as they are. `lanes` replaces the whole list of lanes when given, and so does `rules`. Needs `crm:write` and an active plan.

```bash
curl -X PATCH https://api.fondaro.com/v1/queues/5c1d9f2e-0000-4000-8000-000000000001 \
  -H "Authorization: Bearer $FONDARO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "name": "Today", "rest": "last" }'
```

The answer is the queue after the change.

## Delete a queue

`DELETE /v1/queues/{queueId}`

Delete a queue you made. The leads in it are not touched, and the queue leaves every sidebar it was pinned in. Needs `crm:write` and an active plan.

```bash
curl -X DELETE https://api.fondaro.com/v1/queues/5c1d9f2e-0000-4000-8000-000000000001 \
  -H "Authorization: Bearer $FONDARO_API_KEY"
```

```json
{ "deleted": true, "queueId": "5c1d9f2e-0000-4000-8000-000000000001" }
```

Source: https://www.fondaro.com/docs/api/v1/queues
