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

# Batch digests

> Collect events into batches and send them as one Slack message, aggregated by rules you define, through the API.

A queue collects events your systems push and sends them as one message per batch, so a hundred
deploy notifications become one digest. You create the queue once, push events as they happen,
and the evaluator sends each batch when its window closes. Creating and changing queues needs the
`queues:write` scope (admins and up); pushing events needs `queues:write` too; reading needs
`queues:read`.

Credits are spent per batch when it is sent, never when events are pushed. Each batch costs the
queue's `credit_cost`, 1 unless you set it.

## Create a queue

```bash theme={null}
curl -X POST https://api.lithoblocks.com/v1/queues \
  -H "Authorization: Bearer $LITHOBLOCKS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Deploy digest",
    "template_id": "6b1e…",
    "execution_mode": "fixed_interval",
    "execution_config": { "interval_minutes": 60 },
    "destination": { "channel_id": "C0AS92CQ04D" },
    "aggregation_config": {
      "item_mapping": { "service": "$.service", "version": "$.version" },
      "aggregate_mapping": { "count": { "strategy": "count_events" } }
    }
  }'
```

| Field                | Required | Meaning                                                                 |
| -------------------- | -------- | ----------------------------------------------------------------------- |
| `name`               | yes      | Up to 255 characters.                                                   |
| `description`        | no       | Up to 2000 characters.                                                  |
| `template_id`        | yes      | The template each batch is rendered with.                               |
| `execution_mode`     | yes      | `fixed_interval`, `rolling_window` or `fixed_volume`; see below.        |
| `execution_config`   | yes      | The mode's settings, with exactly the keys the mode takes.              |
| `destination`        | yes      | Exactly one of `channel_id`, `recipient_email` or `recipient_slack_id`. |
| `aggregation_config` | no       | How a batch's events become the template's `data`; see below.           |
| `credit_cost`        | no       | Credits per batch, 1 to 100. Default 1.                                 |
| `is_active`          | no       | Default `true`. `false` creates the queue paused.                       |

The response is `201` with the queue, including `pending_event_count` (events waiting in the
open batch) and, on `GET /v1/queues/{id}`, an `active_batch` summary.

### Execution modes

| Mode             | `execution_config`                                   | Behaviour                                                                                                                    |
| ---------------- | ---------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------- |
| `fixed_interval` | `{ "interval_minutes": 60 }`                         | The batch closes a fixed time after its first event.                                                                         |
| `rolling_window` | `{ "window_minutes": 30 }`                           | Every new event pushes the close out again; a quiet spell of `window_minutes` ends the batch.                                |
| `fixed_volume`   | `{ "event_threshold": 10, "max_wait_minutes": 120 }` | The batch is ready as soon as it holds `event_threshold` events, and closes anyway `max_wait_minutes` after its first event. |

The keys are exact. Anything else, `threshold` or `interval` for instance, is refused with `400`
and a message naming the accepted keys.

### Aggregation

`item_mapping` builds one item per event from paths into the event (`$.field` or `field`), placed
under `items`, or under the key you name in `items_key`. `aggregate_mapping` computes values
across the batch, each entry `{ "strategy": …, "field": "$.path" }`:

| Strategy               | Result                                  | Needs `field` |
| ---------------------- | --------------------------------------- | ------------- |
| `first`, `last`        | The value from the first or last event. | yes           |
| `min`, `max`, `sum`    | Over the numeric values.                | yes           |
| `count_events`         | The number of events in the batch.      | no            |
| `most_frequent`        | The value that occurs most often.       | yes           |
| `any_true`, `all_true` | Whether any, or every, value is truthy. | yes           |

The template then receives, for example,
`{ "items": [{ "service": "api", "version": "1.2" }, …], "count": 7 }`. Without an
`aggregation_config`, the template receives `{ "items": [event, …] }` with the events as pushed.

### Tiers

Which modes and how many active queues your organization may have, and how many events it may
push in 24 hours, depend on its subscription tier. Over any of these lines the API answers `403`
with `code: "tier_limit"` and a message naming the tier and the limit.

| Tier       | Active queues | Events per day | Modes            |
| ---------- | ------------- | -------------- | ---------------- |
| Free       | not available |                |                  |
| Builder    | 3             | 500            | `fixed_interval` |
| Team       | 10            | 5,000          | all three        |
| Business   | 50            | 50,000         | all three        |
| Enterprise | 999           | 999,999        | all three        |

## Push events

```bash theme={null}
curl -X POST https://api.lithoblocks.com/v1/queues/{id}/events \
  -H "Authorization: Bearer $LITHOBLOCKS_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: deploy-run-1234" \
  -d '{ "events": [{ "service": "api", "version": "1.2.0" }] }'
```

Up to 100 events per call, each any JSON object. The events join the queue's open batch, or
start one if nothing is collecting. The response is `202`:

```json theme={null}
{
  "batch_id": "b7e4…",
  "event_ids": ["e1a0…"],
  "batch_status": "collecting",
  "window_end": "2026-09-06T14:12:00.000Z",
  "event_count": 4
}
```

`batch_status` is `ready` when a `fixed_volume` queue has just reached its threshold, otherwise
`collecting`; `window_end` is when the batch will close if nothing else happens. Send an
`Idempotency-Key` so a retried push cannot count the same events twice: the same key and body
within 24 hours replays the original response.

A paused queue (`is_active: false`) refuses events with `409` and `code: "invalid_state"`.

The `queue-ingest` edge function that accepted events before this route is deprecated. It still
answers for existing integrations but takes no new features; move to
`POST /v1/queues/{id}/events`.

## Watch and steer

| Do                                            | Call                                                                                                   |
| --------------------------------------------- | ------------------------------------------------------------------------------------------------------ |
| List queues, with `pending_event_count`       | `GET /v1/queues`, optionally `?is_active=true` or `false`                                              |
| Read one, with its open `active_batch`        | `GET /v1/queues/{id}`                                                                                  |
| Change settings, pause or resume              | `PATCH /v1/queues/{id}`; `is_active` pauses and resumes                                                |
| See what the template would receive right now | `POST /v1/queues/{id}/preview`, returns `{ event_count, aggregated_data }` without sending or spending |
| Send the open batch now                       | `POST /v1/queues/{id}/flush`, the batch goes on the next evaluator tick                                |
| Discard pending events                        | `POST /v1/queues/{id}/drop-pending`, returns how many were dropped                                     |
| Batch history                                 | `GET /v1/queues/{id}/batches`, newest first, `?status=` filters                                        |
| Event history                                 | `GET /v1/queues/{id}/events`, newest first, `?status=` and `?batch_id=` filter                         |
| Every send attempt                            | `GET /v1/queue-executions`, `?queue_id=`, `?scheduled_message_id=` and `?result=` filter               |
| Delete                                        | `DELETE /v1/queues/{id}`                                                                               |

Batch statuses are `collecting`, `ready`, `processing`, `sent` and `failed`; a failed batch
carries `error_message`, typically `insufficient_credits` or `no_slack_connection`. Event
statuses are `pending`, `aggregated` (sent as part of a batch), `expired` and `dropped`. A failed
batch leaves its events `pending`; drop them with `drop-pending` once you have dealt with the
cause.

Executions record each attempt the evaluator made for your organization, batch and scheduled
message alike: `execution_type` (`batch_send`, `scheduled_send`, `constraint_check`,
`manual_send`), `result` (`success`, `failed`, `deferred`), `events_processed`,
`processing_time_ms` and `error_message`.

Deleting a queue is refused with `409` while events are pending; flush or drop them first. Its
batches and executions go with it.
