# Rezolve Brief: agent workflow

Rezolve Brief collects research, prepares a saved digest and sends it only through an explicit send request. Generation can call paid providers. There are no end-user accounts yet: the Bearer key grants operator access.

Use the service's own origin as `BASE_URL`. Read [OpenAPI](/openapi.json) for exact schemas, headers, response codes and operation IDs. Documentation is public and contains synthetic examples; live `/v1/*` data requires `Authorization: Bearer <API_KEY>`. Obtain the key through the operator's configured secret channel. Never put it in URLs, documentation, digest configuration or generated content.

## 1. Read and optionally update configuration

1. `GET /v1/config` returns `{ "config": {...}, "version": "..." }` and an `ETag` header.
2. Edit only the requested fields in that returned `config` object. `PUT /v1/config` replaces the whole configuration; it is not a patch. Send the object itself, without the envelope, with `Content-Type: application/json` and `If-Match: "<version>"` from the read response.
3. On `409 CONFIG_VERSION_CONFLICT`, read the latest configuration and reconcile the intended change before retrying. Missing `If-Match` returns `428`. Do not overwrite concurrent changes or add credentials.

Configuration changes affect future runs. Each saved run keeps its own configuration snapshot. Changing `signals[].listId` selects an external Signals list; this API does not edit that list's members.

## 2. Create one generation request

Before `POST /v1/digests`, confirm that the operator requested a new generation, since it may incur provider costs. Persist a unique `Idempotency-Key` and the exact JSON request body before sending. The key is 1–128 characters matching `^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$`.

Send `Content-Type: application/json`, the saved key and a body such as `{}`. This selects all three sources (`perplexity`, `serper`, `signals`), all three sections (`rezolve`, `competitors`, `market`) and the preceding 24 hours as fixed at the first successful reservation. Explicit `window`, `sources` and `sections` are optional; see OpenAPI for their constraints. Request bodies are limited to 256 KiB.

- `202`: a new run was saved. Persist its `id` and follow `Location`.
- `200`: the same key and request already reserved this run; reuse the returned `id`.
- `409 IDEMPOTENCY_CONFLICT`: the key was used with another request. Resolve the mismatch; do not silently generate a new key.
- `409 GENERATION_BUSY`: another run is active or waiting for the worker. Wait for that run to finish before requesting another.

If the response is lost, replay the saved key and unchanged body to recover the reserved run. Do not create another key merely because of a timeout. A deliberately new generation requires a new key and may incur the costs again. Generation does not send email.

## 3. Poll and review the saved edition

Poll `GET /v1/digests/{id}` with a bounded interval, for example every five seconds. Its statuses are:

| Run status | Next action |
|---|---|
| `running` | Wait; in PostgreSQL this includes waiting for the worker. |
| `ready` | Read the saved edition and inspect its content status. |
| `failed` | Report `errorCode`; do not automatically rerun paid work. |
| `interrupted` | Report the interruption; a new paid run requires an explicit decision. |

`ready` means preparation finished; it is not editorial approval. Inspect `result.digestStatus` (`draft`, `incomplete`, `quiet_day` or `held`), diagnostics and the structured edition at `GET /v1/digests/{id}/json`. Review claims, dates, source links, source coverage and cost uncertainty. The planned automatic editorial validator is not implemented yet.

For a reviewable edition, fetch `GET /v1/digests/{id}/html` or `/text` to see the final reader-facing email. A `held` edition cannot render email or be sent; these operations return `409 EMAIL_EDITION_HELD`. Before the run is ready, edition operations return `409 DIGEST_NOT_READY`. Do not bypass these states by changing configuration or creating replacement runs automatically.

## 4. Send only when explicitly requested

After content review and authorization for the selected edition and recipients, call `POST /v1/digests/{id}/send` with `Content-Type: application/json` and an explicit list of 1–20 unique email addresses:

```json
{"recipients":["reader@example.com"]}
```

One separate email is sent per recipient. The saved edition supplies the content; the server's configured sender and reply-to supply its headers. Do not infer recipients from documentation or send merely because generation finished. Scheduled generation also does not send email.

Inspect every entry in the `recipients` response even when HTTP status is `200`. A recipient outcome can contain an error, an unknown result or a skipped operation. `accepted` means the provider accepted the email; only `delivered` confirms delivery. Preserve `attemptId`, `providerId`, `code` and any `recovery` data when reporting an ambiguous outcome.

The delivery journal identifies an attempt by the saved edition and normalized recipient, independently of the generation key. Accepted, delivered and unknown attempts are suppressed on repeat. Do not change the edition, sender, recipient spelling, generation key or journal to force a resend. `EMAIL_CONTENT_CHANGED` requires operator reconciliation.

## 5. Reconcile delivery before considering retries

`GET /v1/digests/{id}/delivery` reads saved outcomes only; it does not refresh them from Resend. An operator can explicitly reconcile an attempt with `npm run email:status -- --postgres --attempt-id <attempt-id>` in PostgreSQL mode; file-mode status commands are documented in the repository's operator guide.

Never retry an unknown or ambiguous send automatically. After correcting the cause, a known provider rejection may be retried explicitly with the same content; inspect its code and honor `retryAt`. Authentication and rate-limit failures stop the current batch. `BATCH_NOT_ATTEMPTED` means that recipient was not attempted in that batch. Do not treat all `failed` outcomes as safe to retry: a provider ID or ambiguous transport result requires reconciliation.

## Errors and service health

Request errors use `{ "error": { "code": "..." } }`; per-recipient send errors appear inside a successful response instead. OpenAPI lists the operation-specific responses. `401` requires a valid Bearer key. On `503`, inspect the error code and check `GET /health/ready`; the service may lack storage or sender configuration, or be shutting down. `GET /health/live` checks only process liveness. Health endpoints require no key. Neither a failed health check nor an HTTP timeout authorizes a new paid run or an email resend.

---

# Rezolve Brief API

- **OpenAPI Version:** `3.1.0`
- **API Version:** `1.0.0`

Operator API for configuring sources, generating and inspecting saved intelligence digests, and explicitly sending a saved edition. All /v1 operations require a Bearer operator key. Read-only requests do not collect sources or send email. Generation and sending are separate paid actions. Start with /llms.txt and /docs/api.md for the agent workflow. No user accounts or editorial approval endpoint are implemented.

## Contents

**Operations**

- [GET /health/live](#scalar-operation-get-healthlive)
- [GET /health/ready](#scalar-operation-get-healthready)
- [GET /v1/config](#scalar-operation-get-v1config)
- [PUT /v1/config](#scalar-operation-put-v1config)
- [POST /v1/digests](#scalar-operation-post-v1digests)
- [GET /v1/digests](#scalar-operation-get-v1digests)
- [GET /v1/digests/{id}](#scalar-operation-get-v1digestsid)
- [GET /v1/digests/{id}/json](#scalar-operation-get-v1digestsidjson)
- [GET /v1/digests/{id}/html](#scalar-operation-get-v1digestsidhtml)
- [GET /v1/digests/{id}/text](#scalar-operation-get-v1digestsidtext)
- [POST /v1/digests/{id}/send](#scalar-operation-post-v1digestsidsend)
- [GET /v1/digests/{id}/delivery](#scalar-operation-get-v1digestsiddelivery)

**Schemas**

- [Error](#scalar-schema-error)
- [RunWindow](#scalar-schema-runwindow)
- [SearchCompany](#scalar-schema-searchcompany)
- [SearchTopic](#scalar-schema-searchtopic)
- [RssSource](#scalar-schema-rsssource)
- [SignalsSource](#scalar-schema-signalssource)
- [SerperSettings](#scalar-schema-serpersettings)
- [CollectionLimits](#scalar-schema-collectionlimits)
- [AppConfig](#scalar-schema-appconfig)
- [StoredConfig](#scalar-schema-storedconfig)
- [GenerateInput](#scalar-schema-generateinput)
- [RunSummary](#scalar-schema-runsummary)
- [RunLinks](#scalar-schema-runlinks)
- [DigestRun](#scalar-schema-digestrun)
- [DigestRunList](#scalar-schema-digestrunlist)
- [ProviderUsage](#scalar-schema-providerusage)
- [Diagnostic](#scalar-schema-diagnostic)
- [PublicationDate](#scalar-schema-publicationdate)
- [CoverageSource](#scalar-schema-coveragesource)
- [CollectionTotals](#scalar-schema-collectiontotals)
- [SupportedText](#scalar-schema-supportedtext)
- [StoryEvidence](#scalar-schema-storyevidence)
- [DigestStory](#scalar-schema-digeststory)
- [DigestSection](#scalar-schema-digestsection)
- [SearchRequest](#scalar-schema-searchrequest)
- [SerperDiscovery](#scalar-schema-serperdiscovery)
- [SerperDiscoverySource](#scalar-schema-serperdiscoverysource)
- [RenderedDigest](#scalar-schema-rendereddigest)
- [SendInput](#scalar-schema-sendinput)
- [DeliveryRecovery](#scalar-schema-deliveryrecovery)
- [DeliveryOutcome](#scalar-schema-deliveryoutcome)
- [DeliveryList](#scalar-schema-deliverylist)

<a id="scalar-context-global-servers"></a>

## Servers

- **URL:** `/`
  - **Description:** This service origin

## Tags

### Health

Public process and storage probes.

### Configuration

Read and replace the complete non-secret configuration using optimistic concurrency.

### Digests

Create one durable asynchronous run, poll it and inspect the saved edition.

### Delivery

Explicit per-recipient sending and read-only saved outcome inspection.

## Operations

<a id="scalar-operation-get-healthlive"></a>

### Check process liveness

- **Method:** `GET`
- **Path:** `/health/live`
- **Operation ID:** `getLiveness`
- **Tags:** Health

**Servers:** [Inherited servers](#scalar-context-global-servers)

#### Authentication

No authentication required.

#### Responses

##### 200 Process answers HTTP.

**Content type:** `application/json`

no additional properties

- **`status` (required)**: `string`, const: `"ok"`

<a id="scalar-example-1"></a>

**Generated example:**

```json
{
  "status": "ok"
}
```

<a id="scalar-operation-get-healthready"></a>

### Check storage readiness

- **Method:** `GET`
- **Path:** `/health/ready`
- **Operation ID:** `getReadiness`
- **Tags:** Health

Checks PostgreSQL readiness or file-store read/write access. This probe does not verify provider credentials, source coverage, worker progress or email delivery.

**Servers:** [Inherited servers](#scalar-context-global-servers)

#### Authentication

No authentication required.

#### Responses

##### 200 Ready for API requests.

**Content type:** `application/json`

no additional properties

- **`status` (required)**: `string`, const: `"ready"`

<a id="scalar-example-2"></a>

**Generated example:**

```json
{
  "status": "ready"
}
```

##### 503 STORAGE_UNAVAILABLE or SHUTTING_DOWN.

**Content type:** `application/json`

[Error](#scalar-schema-error)

<a id="scalar-example-3"></a>

**Generated example:**

```json
{
  "error": {
    "code": ""
  }
}
```

<a id="scalar-operation-get-v1config"></a>

### Read the current configuration

- **Method:** `GET`
- **Path:** `/v1/config`
- **Operation ID:** `getConfig`
- **Tags:** Configuration

Returns {config, version} and a quoted ETag. To update, modify config and PUT only that object with If-Match set to the ETag.

**Servers:** [Inherited servers](#scalar-context-global-servers)

#### Authentication

- **bearerAuth**: HTTP bearer

  Operator API_KEY supplied out of band. Use Authorization: Bearer \<API_KEY>. Never put it in a URL, documentation, config body or logs. One operator key grants read, config, paid generation and explicit send access; no user roles exist yet.

#### Responses

##### 200 Current non-secret configuration.

**Headers:**

- **`ETag`**

  Quoted current configuration version, to return unchanged in If-Match.

  `string`, pattern: `^"[a-f0-9]{64}"$`

  **Example:**

  ```json
  "\"9dcd53d4b3a53977e6dcc7c6a93ff8238ed6bf38c18b70f0900eeaeb1ac0b5c7\""
  ```

**Content type:** `application/json`

[StoredConfig](#scalar-schema-storedconfig)

**Example:**

```json
{
  "config": {
    "version": 1,
    "name": "Example Brief",
    "timezone": "America/Los_Angeles",
    "sendTime": "06:00",
    "maxPreviewItems": 8,
    "topics": [
      {
        "id": "rezolve",
        "title": "Company news",
        "description": "Example company announcements.",
        "allowedAnchors": [
          "Example Commerce"
        ],
        "allowedQualifiers": [
          "news"
        ],
        "requiredAnchors": [
          "Example Commerce"
        ],
        "companies": [
          {
            "id": "example-commerce",
            "name": "Example Commerce"
          }
        ]
      },
      {
        "id": "competitors",
        "title": "Competitor news",
        "description": "Example competitor announcements.",
        "allowedAnchors": [
          "Example Search"
        ],
        "allowedQualifiers": [
          "news"
        ],
        "requiredAnchors": [
          "Example Search"
        ],
        "companies": [
          {
            "id": "example-search",
            "name": "Example Search"
          }
        ]
      },
      {
        "id": "market",
        "title": "Market news",
        "description": "Retail technology announcements.",
        "allowedAnchors": [
          "retail technology"
        ],
        "allowedQualifiers": [
          "news"
        ],
        "requiredAnchors": [
          "retail technology"
        ],
        "searchQueries": [
          "retail technology news"
        ]
      }
    ],
    "rss": [],
    "signals": [],
    "limits": {
      "timeoutMs": 30000,
      "maxSearchRequests": 3,
      "maxLlmRequests": 2,
      "maxOutputTokens": 1200,
      "maxSearchResults": 10
    }
  },
  "version": "9dcd53d4b3a53977e6dcc7c6a93ff8238ed6bf38c18b70f0900eeaeb1ac0b5c7"
}
```

##### 401 UNAUTHORIZED: missing or invalid Bearer token.

**Headers:**

- **`WWW-Authenticate`**

  `string`

  **Example:**

  ```json
  "Bearer"
  ```

**Content type:** `application/json`

[Error](#scalar-schema-error)

[Generated example](#scalar-example-3)

##### 413, 500

- `413` BODY_TOO_LARGE: request body exceeds 256 KiB.
- `500` Server/storage failure, e.g. OPERATION_FAILED. Inspect the sanitized server log; do not automatically repeat a paid operation.

**Content type:** `application/json`

[Error](#scalar-schema-error)

[Generated example](#scalar-example-3)

<a id="scalar-operation-put-v1config"></a>

### Replace the complete configuration

- **Method:** `PUT`
- **Path:** `/v1/config`
- **Operation ID:** `replaceConfig`
- **Tags:** Configuration

Atomic full replacement, not a patch. Existing runs retain their saved config and version. Unknown fields and active credential values are rejected. On 409, reread and reconcile; do not blindly overwrite. Config updates do not enable the schedule or change membership of a Signals list.

**Servers:** [Inherited servers](#scalar-context-global-servers)

#### Authentication

- **bearerAuth**: HTTP bearer

  Operator API_KEY supplied out of band. Use Authorization: Bearer \<API_KEY>. Never put it in a URL, documentation, config body or logs. One operator key grants read, config, paid generation and explicit send access; no user roles exist yet.

#### Header parameters

- **`If-Match` (required)**: `string`, pattern: `^"?[a-f0-9]{64}"?$`

  Use the quoted ETag from GET /v1/config. Bare SHA-256 is also accepted.

  **Example:**

  ```json
  "\"9dcd53d4b3a53977e6dcc7c6a93ff8238ed6bf38c18b70f0900eeaeb1ac0b5c7\""
  ```

#### Request body

**Required:** `true`

**Content type:** `application/json`

[AppConfig](#scalar-schema-appconfig)

Complete non-secret configuration, not a patch. timezone must be supported by Intl.DateTimeFormat (normally an IANA timezone). topics must contain rezolve, competitors and market exactly once. Source IDs must be unique across rss and signals. Unknown fields and values containing active credentials are rejected. Credentials are managed outside this API.

**Example:**

```json
{
  "version": 1,
  "name": "Example Brief",
  "timezone": "America/Los_Angeles",
  "sendTime": "06:00",
  "maxPreviewItems": 8,
  "topics": [
    {
      "id": "rezolve",
      "title": "Company news",
      "description": "Example company announcements.",
      "allowedAnchors": [
        "Example Commerce"
      ],
      "allowedQualifiers": [
        "news"
      ],
      "requiredAnchors": [
        "Example Commerce"
      ],
      "companies": [
        {
          "id": "example-commerce",
          "name": "Example Commerce"
        }
      ]
    },
    {
      "id": "competitors",
      "title": "Competitor news",
      "description": "Example competitor announcements.",
      "allowedAnchors": [
        "Example Search"
      ],
      "allowedQualifiers": [
        "news"
      ],
      "requiredAnchors": [
        "Example Search"
      ],
      "companies": [
        {
          "id": "example-search",
          "name": "Example Search"
        }
      ]
    },
    {
      "id": "market",
      "title": "Market news",
      "description": "Retail technology announcements.",
      "allowedAnchors": [
        "retail technology"
      ],
      "allowedQualifiers": [
        "news"
      ],
      "requiredAnchors": [
        "retail technology"
      ],
      "searchQueries": [
        "retail technology news"
      ]
    }
  ],
  "rss": [],
  "signals": [],
  "limits": {
    "timeoutMs": 30000,
    "maxSearchRequests": 3,
    "maxLlmRequests": 2,
    "maxOutputTokens": 1200,
    "maxSearchResults": 10
  }
}
```

#### Responses

##### 200 Saved configuration and new version.

**Headers:**

- **`ETag`**

  Quoted current configuration version, to return unchanged in If-Match.

  `string`, pattern: `^"[a-f0-9]{64}"$`

  **Example:**

  ```json
  "\"9dcd53d4b3a53977e6dcc7c6a93ff8238ed6bf38c18b70f0900eeaeb1ac0b5c7\""
  ```

**Content type:** `application/json`

[StoredConfig](#scalar-schema-storedconfig)

**Example:**

```json
{
  "config": {
    "version": 1,
    "name": "Example Brief",
    "timezone": "America/Los_Angeles",
    "sendTime": "06:00",
    "maxPreviewItems": 8,
    "topics": [
      {
        "id": "rezolve",
        "title": "Company news",
        "description": "Example company announcements.",
        "allowedAnchors": [
          "Example Commerce"
        ],
        "allowedQualifiers": [
          "news"
        ],
        "requiredAnchors": [
          "Example Commerce"
        ],
        "companies": [
          {
            "id": "example-commerce",
            "name": "Example Commerce"
          }
        ]
      },
      {
        "id": "competitors",
        "title": "Competitor news",
        "description": "Example competitor announcements.",
        "allowedAnchors": [
          "Example Search"
        ],
        "allowedQualifiers": [
          "news"
        ],
        "requiredAnchors": [
          "Example Search"
        ],
        "companies": [
          {
            "id": "example-search",
            "name": "Example Search"
          }
        ]
      },
      {
        "id": "market",
        "title": "Market news",
        "description": "Retail technology announcements.",
        "allowedAnchors": [
          "retail technology"
        ],
        "allowedQualifiers": [
          "news"
        ],
        "requiredAnchors": [
          "retail technology"
        ],
        "searchQueries": [
          "retail technology news"
        ]
      }
    ],
    "rss": [],
    "signals": [],
    "limits": {
      "timeoutMs": 30000,
      "maxSearchRequests": 3,
      "maxLlmRequests": 2,
      "maxOutputTokens": 1200,
      "maxSearchResults": 10
    }
  },
  "version": "9dcd53d4b3a53977e6dcc7c6a93ff8238ed6bf38c18b70f0900eeaeb1ac0b5c7"
}
```

##### 400, 409, 413, 415, 428, 500, 503

- `400` INVALID_JSON, INVALID_CONFIG_VERSION, INVALID_CONFIG or SECRET_IN_CONFIG.
- `409` CONFIG_VERSION_CONFLICT: configuration changed since it was read.
- `413` BODY_TOO_LARGE: request body exceeds 256 KiB.
- `415` JSON_REQUIRED: use Content-Type: application/json.
- `428` CONFIG_VERSION_REQUIRED: If-Match is missing.
- `500` Server/storage failure, e.g. OPERATION_FAILED. Inspect the sanitized server log; do not automatically repeat a paid operation.
- `503` SHUTTING_DOWN or service unavailable. A request timeout/failure does not prove no work occurred; reconcile using the existing idempotency key.

**Content type:** `application/json`

[Error](#scalar-schema-error)

[Generated example](#scalar-example-3)

##### 401 UNAUTHORIZED: missing or invalid Bearer token.

**Headers:**

- **`WWW-Authenticate`**

  `string`

  **Example:**

  ```json
  "Bearer"
  ```

**Content type:** `application/json`

[Error](#scalar-schema-error)

[Generated example](#scalar-example-3)

<a id="scalar-operation-post-v1digests"></a>

### Create or replay a digest generation request

- **Method:** `POST`
- **Path:** `/v1/digests`
- **Operation ID:** `createDigest`
- **Tags:** Digests

Paid operation. A new request persists ID, exact window and a config snapshot before provider calls. PostgreSQL run+job are committed atomically and processed by the worker; files mode starts work in-process. The same key and semantically equivalent request returns the same run, even after restart/current-config changes. A different request with the same key returns 409. Only one running or worker-pending run is allowed; no backlog is accumulated. After transport ambiguity, replay the same key/body. A new key means a new paid run. This endpoint never sends email or marks stories as shown.

**Servers:** [Inherited servers](#scalar-context-global-servers)

#### Authentication

- **bearerAuth**: HTTP bearer

  Operator API_KEY supplied out of band. Use Authorization: Bearer \<API_KEY>. Never put it in a URL, documentation, config body or logs. One operator key grants read, config, paid generation and explicit send access; no user roles exist yet.

#### Header parameters

- **`Idempotency-Key` (required)**: `string`, minLength: `1`, maxLength: `128`, pattern: `^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$`

  **Example:**

  ```json
  "agent-preview-2026-10-28-01"
  ```

#### Request body

**Required:** `true`

**Content type:** `application/json`

[GenerateInput](#scalar-schema-generateinput)

Use {} for the default sources/sections and the preceding 24 hours fixed at the first request. RSS is available only when explicitly requested. A new idempotency key can incur new provider charges. Generation never sends email.

**Example:**

```json
{}
```

#### Responses

##### 200 Existing run replayed; no new generation started.

**Headers:**

- **`Location`**

  Relative URL of this saved run.

  `string`

**Content type:** `application/json`

[DigestRun](#scalar-schema-digestrun)

running includes waiting for a PostgreSQL worker. Poll the self link until ready, failed or interrupted. ready means persisted output exists, not editorial approval; inspect result.digestStatus and the edition. failed/interrupted are terminal for this ID. There is no automatic rerun of claimed paid work. A repeated idempotency key returns this run and its saved configuration/window.

**Example:**

```json
{
  "id": "11111111-1111-4111-8111-111111111111",
  "status": "running",
  "createdAt": "2026-10-28T13:00:00.000Z",
  "updatedAt": "2026-10-28T13:00:00.000Z",
  "configVersion": "9dcd53d4b3a53977e6dcc7c6a93ff8238ed6bf38c18b70f0900eeaeb1ac0b5c7",
  "window": {
    "from": "2026-10-27T13:00:00.000Z",
    "to": "2026-10-28T13:00:00.000Z"
  },
  "sources": [
    "perplexity",
    "serper",
    "signals"
  ],
  "sections": [
    "rezolve",
    "competitors",
    "market"
  ],
  "result": null,
  "errorCode": null,
  "links": {
    "self": "/v1/digests/11111111-1111-4111-8111-111111111111",
    "json": "/v1/digests/11111111-1111-4111-8111-111111111111/json",
    "html": "/v1/digests/11111111-1111-4111-8111-111111111111/html",
    "text": "/v1/digests/11111111-1111-4111-8111-111111111111/text",
    "delivery": "/v1/digests/11111111-1111-4111-8111-111111111111/delivery"
  }
}
```

##### 202 New asynchronous run saved; poll Location or links.self.

**Headers:**

- **`Location`**

  Relative URL of this saved run.

  `string`

**Content type:** `application/json`

[DigestRun](#scalar-schema-digestrun)

running includes waiting for a PostgreSQL worker. Poll the self link until ready, failed or interrupted. ready means persisted output exists, not editorial approval; inspect result.digestStatus and the edition. failed/interrupted are terminal for this ID. There is no automatic rerun of claimed paid work. A repeated idempotency key returns this run and its saved configuration/window.

**Example:**

```json
{
  "id": "11111111-1111-4111-8111-111111111111",
  "status": "running",
  "createdAt": "2026-10-28T13:00:00.000Z",
  "updatedAt": "2026-10-28T13:00:00.000Z",
  "configVersion": "9dcd53d4b3a53977e6dcc7c6a93ff8238ed6bf38c18b70f0900eeaeb1ac0b5c7",
  "window": {
    "from": "2026-10-27T13:00:00.000Z",
    "to": "2026-10-28T13:00:00.000Z"
  },
  "sources": [
    "perplexity",
    "serper",
    "signals"
  ],
  "sections": [
    "rezolve",
    "competitors",
    "market"
  ],
  "result": null,
  "errorCode": null,
  "links": {
    "self": "/v1/digests/11111111-1111-4111-8111-111111111111",
    "json": "/v1/digests/11111111-1111-4111-8111-111111111111/json",
    "html": "/v1/digests/11111111-1111-4111-8111-111111111111/html",
    "text": "/v1/digests/11111111-1111-4111-8111-111111111111/text",
    "delivery": "/v1/digests/11111111-1111-4111-8111-111111111111/delivery"
  }
}
```

##### 400, 409, 413, 415, 500, 503

- `400` INVALID_JSON, INVALID_REQUEST, INVALID_GENERATION_INPUT or INVALID_IDEMPOTENCY_KEY.
- `409` IDEMPOTENCY_CONFLICT or GENERATION_BUSY. For busy, inspect the current run and retry explicitly when appropriate.
- `413` BODY_TOO_LARGE: request body exceeds 256 KiB.
- `415` JSON_REQUIRED: use Content-Type: application/json.
- `500` Server/storage failure, e.g. OPERATION_FAILED. Inspect the sanitized server log; do not automatically repeat a paid operation.
- `503` SHUTTING_DOWN or service unavailable. A request timeout/failure does not prove no work occurred; reconcile using the existing idempotency key.

**Content type:** `application/json`

[Error](#scalar-schema-error)

[Generated example](#scalar-example-3)

##### 401 UNAUTHORIZED: missing or invalid Bearer token.

**Headers:**

- **`WWW-Authenticate`**

  `string`

  **Example:**

  ```json
  "Bearer"
  ```

**Content type:** `application/json`

[Error](#scalar-schema-error)

[Generated example](#scalar-example-3)

<a id="scalar-operation-get-v1digests"></a>

### List saved runs, newest first

- **Method:** `GET`
- **Path:** `/v1/digests`
- **Operation ID:** `listDigests`
- **Tags:** Digests

**Servers:** [Inherited servers](#scalar-context-global-servers)

#### Authentication

- **bearerAuth**: HTTP bearer

  Operator API_KEY supplied out of band. Use Authorization: Bearer \<API_KEY>. Never put it in a URL, documentation, config body or logs. One operator key grants read, config, paid generation and explicit send access; no user roles exist yet.

#### Query parameters

- **`limit`**: `integer`, default: `50`, minimum: `1`, maximum: `100`

#### Responses

##### 200 Latest saved runs. No cursor pagination is currently available.

**Content type:** `application/json`

[DigestRunList](#scalar-schema-digestrunlist)

**Example:**

```json
{
  "items": []
}
```

##### 400, 413, 500

- `400` INVALID_LIMIT: limit must be an integer from 1 to 100.
- `413` BODY_TOO_LARGE: request body exceeds 256 KiB.
- `500` Server/storage failure, e.g. OPERATION_FAILED. Inspect the sanitized server log; do not automatically repeat a paid operation.

**Content type:** `application/json`

[Error](#scalar-schema-error)

[Generated example](#scalar-example-3)

##### 401 UNAUTHORIZED: missing or invalid Bearer token.

**Headers:**

- **`WWW-Authenticate`**

  `string`

  **Example:**

  ```json
  "Bearer"
  ```

**Content type:** `application/json`

[Error](#scalar-schema-error)

[Generated example](#scalar-example-3)

<a id="scalar-operation-get-v1digestsid"></a>

### Read a saved run and its status

- **Method:** `GET`
- **Path:** `/v1/digests/{id}`
- **Operation ID:** `getDigestRun`
- **Tags:** Digests

Safe to poll. Stops at ready, failed or interrupted. ready does not mean reviewed or approved. Check result.digestStatus, coverage and evidence before any explicit send.

**Servers:** [Inherited servers](#scalar-context-global-servers)

#### Authentication

- **bearerAuth**: HTTP bearer

  Operator API_KEY supplied out of band. Use Authorization: Bearer \<API_KEY>. Never put it in a URL, documentation, config body or logs. One operator key grants read, config, paid generation and explicit send access; no user roles exist yet.

#### Path parameters

- **`id` (required)**: `string`, format: `uuid`, pattern: `^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$`

  Saved API run UUID from POST /v1/digests; not the composition runId or email textHash.

  **Example:**

  ```json
  "11111111-1111-4111-8111-111111111111"
  ```

#### Responses

##### 200 Saved run status and artifact links.

**Content type:** `application/json`

[DigestRun](#scalar-schema-digestrun)

running includes waiting for a PostgreSQL worker. Poll the self link until ready, failed or interrupted. ready means persisted output exists, not editorial approval; inspect result.digestStatus and the edition. failed/interrupted are terminal for this ID. There is no automatic rerun of claimed paid work. A repeated idempotency key returns this run and its saved configuration/window.

**Example:**

```json
{
  "id": "11111111-1111-4111-8111-111111111111",
  "status": "running",
  "createdAt": "2026-10-28T13:00:00.000Z",
  "updatedAt": "2026-10-28T13:00:00.000Z",
  "configVersion": "9dcd53d4b3a53977e6dcc7c6a93ff8238ed6bf38c18b70f0900eeaeb1ac0b5c7",
  "window": {
    "from": "2026-10-27T13:00:00.000Z",
    "to": "2026-10-28T13:00:00.000Z"
  },
  "sources": [
    "perplexity",
    "serper",
    "signals"
  ],
  "sections": [
    "rezolve",
    "competitors",
    "market"
  ],
  "result": null,
  "errorCode": null,
  "links": {
    "self": "/v1/digests/11111111-1111-4111-8111-111111111111",
    "json": "/v1/digests/11111111-1111-4111-8111-111111111111/json",
    "html": "/v1/digests/11111111-1111-4111-8111-111111111111/html",
    "text": "/v1/digests/11111111-1111-4111-8111-111111111111/text",
    "delivery": "/v1/digests/11111111-1111-4111-8111-111111111111/delivery"
  }
}
```

##### 401 UNAUTHORIZED: missing or invalid Bearer token.

**Headers:**

- **`WWW-Authenticate`**

  `string`

  **Example:**

  ```json
  "Bearer"
  ```

**Content type:** `application/json`

[Error](#scalar-schema-error)

[Generated example](#scalar-example-3)

##### 404, 413, 500

- `404` RUN_NOT_FOUND: no run with this UUID.
- `413` BODY_TOO_LARGE: request body exceeds 256 KiB.
- `500` Server/storage failure, e.g. OPERATION_FAILED. Inspect the sanitized server log; do not automatically repeat a paid operation.

**Content type:** `application/json`

[Error](#scalar-schema-error)

[Generated example](#scalar-example-3)

<a id="scalar-operation-get-v1digestsidjson"></a>

### Read structured edition, evidence and diagnostics

- **Method:** `GET`
- **Path:** `/v1/digests/{id}/json`
- **Operation ID:** `getDigestJson`
- **Tags:** Digests

Reads persisted output only. JSON remains available for a held edition; it does not trigger collection, composition or email. Inspect source queries, publication dates, evidence, coverage and costs here.

**Servers:** [Inherited servers](#scalar-context-global-servers)

#### Authentication

- **bearerAuth**: HTTP bearer

  Operator API_KEY supplied out of band. Use Authorization: Bearer \<API_KEY>. Never put it in a URL, documentation, config body or logs. One operator key grants read, config, paid generation and explicit send access; no user roles exist yet.

#### Path parameters

- **`id` (required)**: `string`, format: `uuid`, pattern: `^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$`

  Saved API run UUID from POST /v1/digests; not the composition runId or email textHash.

  **Example:**

  ```json
  "11111111-1111-4111-8111-111111111111"
  ```

#### Responses

##### 200 Structured saved edition.

**Content type:** `application/json`

[RenderedDigest](#scalar-schema-rendereddigest)

Structured saved edition with evidence, historical source/query inspection and diagnostics. This is not the complete internal composition bundle. A held edition has no publishable stories; its JSON remains inspectable. The final reader-facing email is returned by /html and /text. All editions require editorial review; the automated validator is not implemented yet.

<a id="scalar-example-4"></a>

**Generated example:**

```json
{
  "version": 1,
  "kind": "composed-digest",
  "composerVersion": "r1-compose-1",
  "textVersion": "r1-digest-text-1",
  "runId": "",
  "createdAt": "",
  "mode": "live",
  "status": "draft",
  "reviewStatus": "unreviewed",
  "language": "en",
  "window": {
    "from": "2026-10-27T13:00:00.000Z",
    "to": "2026-10-28T13:00:00.000Z"
  },
  "configHash": "",
  "sourceHashEncoding": "canonical-json-v1",
  "sourceSnapshotHash": "",
  "selectionHash": "",
  "selectionRunId": "",
  "originalSelection": {
    "runId": "",
    "usage": {
      "requestCount": 0,
      "inputTokens": 0,
      "outputTokens": 0,
      "costUsd": 0,
      "knownCostUsd": 0,
      "estimatedCostUsd": 0
    }
  },
  "originRunId": "",
  "usage": {
    "requestCount": 0,
    "inputTokens": 0,
    "outputTokens": 0,
    "costUsd": 0,
    "knownCostUsd": 0,
    "estimatedCostUsd": 0
  },
  "originalComposition": {
    "runId": "",
    "usage": {
      "requestCount": 0,
      "inputTokens": 0,
      "outputTokens": 0,
      "costUsd": 0,
      "knownCostUsd": 0,
      "estimatedCostUsd": 0
    }
  },
  "collectionTotals": {
    "collectedItems": 0,
    "displayedItems": 0,
    "sourceRequests": 0,
    "knownCostUsd": 0,
    "estimatedCostUsd": 0,
    "costUsd": 0,
    "unknownCostSources": 0
  },
  "coverageIncomplete": true,
  "coverage": [
    {
      "sourceId": "",
      "provider": "perplexity",
      "section": "rezolve",
      "status": "ok",
      "diagnostics": [
        {
          "code": "",
          "message": ""
        }
      ],
      "usage": {
        "requestCount": 0,
        "inputTokens": 0,
        "outputTokens": 0,
        "costUsd": 0,
        "knownCostUsd": 0,
        "estimatedCostUsd": 0
      },
      "elapsedMs": 0
    }
  ],
  "diagnostics": [
    ""
  ],
  "wordCount": 0,
  "wordCountScope": "reader-edition-excluding-operator-appendix",
  "textHashAlgorithm": "sha256",
  "textHash": "",
  "bodyText": "",
  "sections": [
    {
      "id": "rezolve",
      "title": "",
      "items": [
        {
          "storyId": "",
          "headline": {
            "text": "",
            "evidenceIds": [
              ""
            ]
          },
          "summary": {
            "text": "",
            "evidenceIds": [
              ""
            ]
          },
          "whyItMatters": "",
          "section": "rezolve",
          "kind": "new",
          "updateOf": "",
          "sourceKind": "company_release",
          "url": "",
          "sourceTitle": "",
          "publicationDate": {
            "value": "",
            "precision": "instant"
          },
          "dateReviewRequired": true,
          "evidence": [
            {
              "id": "",
              "candidateId": "",
              "occurrenceId": "",
              "field": "title",
              "quote": "",
              "url": "",
              "publishedAt": {
                "value": "",
                "precision": "instant"
              },
              "publisher": ""
            }
          ]
        }
      ]
    }
  ],
  "searchRequestsScope": "collection",
  "searchRequests": [
    {
      "provider": "perplexity",
      "sourceId": "",
      "section": "rezolve",
      "recordingStatus": "recorded",
      "queries": [
        ""
      ]
    }
  ],
  "serperDiscoverySources": [
    {
      "sourceId": "",
      "section": "rezolve",
      "searchRequests": 0,
      "articleRequests": 0,
      "articleCacheHits": 0,
      "credits": 0,
      "costUsd": 0,
      "estimatedCostUsd": 0,
      "discoveries": [
        {
          "url": "",
          "title": "",
          "providerDate": "",
          "publishedAt": {
            "value": "",
            "precision": "instant"
          },
          "status": "accepted",
          "reason": "",
          "linkedExistingUrl": ""
        }
      ]
    }
  ]
}
```

##### 401 UNAUTHORIZED: missing or invalid Bearer token.

**Headers:**

- **`WWW-Authenticate`**

  `string`

  **Example:**

  ```json
  "Bearer"
  ```

**Content type:** `application/json`

[Error](#scalar-schema-error)

[Generated example](#scalar-example-3)

##### 404, 409, 413, 500

- `404` RUN_NOT_FOUND: no run with this UUID.
- `409` DIGEST_NOT_READY: generation has not completed successfully.
- `413` BODY_TOO_LARGE: request body exceeds 256 KiB.
- `500` Server/storage failure, e.g. OPERATION_FAILED. Inspect the sanitized server log; do not automatically repeat a paid operation.

**Content type:** `application/json`

[Error](#scalar-schema-error)

[Generated example](#scalar-example-3)

<a id="scalar-operation-get-v1digestsidhtml"></a>

### Read the final email HTML

- **Method:** `GET`
- **Path:** `/v1/digests/{id}/html`
- **Operation ID:** `getDigestHtml`
- **Tags:** Digests

Renders the saved reader-facing edition without sender credentials. A held edition cannot render as an email. This does not send email.

**Servers:** [Inherited servers](#scalar-context-global-servers)

#### Authentication

- **bearerAuth**: HTTP bearer

  Operator API_KEY supplied out of band. Use Authorization: Bearer \<API_KEY>. Never put it in a URL, documentation, config body or logs. One operator key grants read, config, paid generation and explicit send access; no user roles exist yet.

#### Path parameters

- **`id` (required)**: `string`, format: `uuid`, pattern: `^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$`

  Saved API run UUID from POST /v1/digests; not the composition runId or email textHash.

  **Example:**

  ```json
  "11111111-1111-4111-8111-111111111111"
  ```

#### Responses

##### 200 Reader-facing email HTML.

**Content type:** `text/html`

`string`

<a id="scalar-example-5"></a>

**Generated example:**

```json
""
```

##### 401 UNAUTHORIZED: missing or invalid Bearer token.

**Headers:**

- **`WWW-Authenticate`**

  `string`

  **Example:**

  ```json
  "Bearer"
  ```

**Content type:** `application/json`

[Error](#scalar-schema-error)

[Generated example](#scalar-example-3)

##### 404, 409, 413, 500

- `404` RUN_NOT_FOUND: no run with this UUID.
- `409` DIGEST_NOT_READY or EMAIL_EDITION_HELD.
- `413` BODY_TOO_LARGE: request body exceeds 256 KiB.
- `500` Server/storage failure, e.g. OPERATION_FAILED. Inspect the sanitized server log; do not automatically repeat a paid operation.

**Content type:** `application/json`

[Error](#scalar-schema-error)

[Generated example](#scalar-example-3)

<a id="scalar-operation-get-v1digestsidtext"></a>

### Read the final email text

- **Method:** `GET`
- **Path:** `/v1/digests/{id}/text`
- **Operation ID:** `getDigestText`
- **Tags:** Digests

Renders the saved reader-facing edition without sender credentials. A held edition cannot render as an email. This does not send email.

**Servers:** [Inherited servers](#scalar-context-global-servers)

#### Authentication

- **bearerAuth**: HTTP bearer

  Operator API_KEY supplied out of band. Use Authorization: Bearer \<API_KEY>. Never put it in a URL, documentation, config body or logs. One operator key grants read, config, paid generation and explicit send access; no user roles exist yet.

#### Path parameters

- **`id` (required)**: `string`, format: `uuid`, pattern: `^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$`

  Saved API run UUID from POST /v1/digests; not the composition runId or email textHash.

  **Example:**

  ```json
  "11111111-1111-4111-8111-111111111111"
  ```

#### Responses

##### 200 Reader-facing plain-text email.

**Content type:** `text/plain`

`string`

<a id="scalar-example-6"></a>

**Generated example:**

```json
""
```

##### 401 UNAUTHORIZED: missing or invalid Bearer token.

**Headers:**

- **`WWW-Authenticate`**

  `string`

  **Example:**

  ```json
  "Bearer"
  ```

**Content type:** `application/json`

[Error](#scalar-schema-error)

[Generated example](#scalar-example-3)

##### 404, 409, 413, 500

- `404` RUN_NOT_FOUND: no run with this UUID.
- `409` DIGEST_NOT_READY or EMAIL_EDITION_HELD.
- `413` BODY_TOO_LARGE: request body exceeds 256 KiB.
- `500` Server/storage failure, e.g. OPERATION_FAILED. Inspect the sanitized server log; do not automatically repeat a paid operation.

**Content type:** `application/json`

[Error](#scalar-schema-error)

[Generated example](#scalar-example-3)

<a id="scalar-operation-post-v1digestsidsend"></a>

### Explicitly send a saved edition to selected recipients

- **Method:** `POST`
- **Path:** `/v1/digests/{id}/send`
- **Operation ID:** `sendDigest`
- **Tags:** Delivery

External side effect: sends one separate email per address. Obtain operator approval for the specific edition and recipients before using it. Uses saved content and server sender settings. Identity is edition+normalized recipient; accepted/delivered or ambiguous attempts are not automatically resent. A body/sender change on an existing reservation is EMAIL_CONTENT_CHANGED. Per-recipient outcomes may contain failures even with HTTP 200. accepted is not delivered. Unknown results require reconciliation. Explicit retries are allowed only for known rejected attempts with unchanged content; honor retryAt. Sending does not regenerate the edition or mark stories as shown.

**Servers:** [Inherited servers](#scalar-context-global-servers)

#### Authentication

- **bearerAuth**: HTTP bearer

  Operator API_KEY supplied out of band. Use Authorization: Bearer \<API_KEY>. Never put it in a URL, documentation, config body or logs. One operator key grants read, config, paid generation and explicit send access; no user roles exist yet.

#### Path parameters

- **`id` (required)**: `string`, format: `uuid`, pattern: `^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$`

  Saved API run UUID from POST /v1/digests; not the composition runId or email textHash.

  **Example:**

  ```json
  "11111111-1111-4111-8111-111111111111"
  ```

#### Request body

**Required:** `true`

**Content type:** `application/json`

[SendInput](#scalar-schema-sendinput)

Explicit recipients for this request. All addresses are validated before the first send; duplicates are rejected after lowercasing. Each recipient gets a separate email. The sender and reply-to come from server environment, not this body.

**Example:**

```json
{
  "recipients": [
    "reader@example.com"
  ]
}
```

#### Responses

##### 200 Inspect every recipient outcome; this HTTP status does not guarantee all messages were accepted or delivered.

**Content type:** `application/json`

[DeliveryList](#scalar-schema-deliverylist)

**Example:**

```json
{
  "recipients": [
    {
      "recipient": "reader@example.com",
      "attemptId": "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
      "status": "accepted",
      "providerId": "22222222-2222-4222-8222-222222222222",
      "suppressed": false,
      "requests": 1,
      "code": null
    }
  ]
}
```

##### 400, 404, 409, 413, 415, 500, 503

- `400` INVALID_JSON, INVALID_REQUEST, INVALID_RECIPIENTS, INVALID_EMAIL_CONFIG or an email preparation error.
- `404` RUN_NOT_FOUND: no run with this UUID.
- `409` DIGEST_NOT_READY or EMAIL_EDITION_HELD.
- `413` BODY_TOO_LARGE: request body exceeds 256 KiB.
- `415` JSON_REQUIRED: use Content-Type: application/json.
- `500` Server/storage failure, e.g. OPERATION_FAILED. Inspect the sanitized server log; do not automatically repeat a paid operation.
- `503` RESEND_CONFIG_MISSING or SHUTTING_DOWN.

**Content type:** `application/json`

[Error](#scalar-schema-error)

[Generated example](#scalar-example-3)

##### 401 UNAUTHORIZED: missing or invalid Bearer token.

**Headers:**

- **`WWW-Authenticate`**

  `string`

  **Example:**

  ```json
  "Bearer"
  ```

**Content type:** `application/json`

[Error](#scalar-schema-error)

[Generated example](#scalar-example-3)

<a id="scalar-operation-get-v1digestsiddelivery"></a>

### Read saved per-recipient delivery outcomes

- **Method:** `GET`
- **Path:** `/v1/digests/{id}/delivery`
- **Operation ID:** `getDigestDeliveries`
- **Tags:** Delivery

Reads the saved operator ledger, with no request to Resend and no email side effect. The list is empty before the first persisted attempt. accepted alone does not prove delivery; provider status reconciliation is currently an explicit operator CLI action. Read outcomes are suppressed=true and requests=0 because no send is performed.

**Servers:** [Inherited servers](#scalar-context-global-servers)

#### Authentication

- **bearerAuth**: HTTP bearer

  Operator API_KEY supplied out of band. Use Authorization: Bearer \<API_KEY>. Never put it in a URL, documentation, config body or logs. One operator key grants read, config, paid generation and explicit send access; no user roles exist yet.

#### Path parameters

- **`id` (required)**: `string`, format: `uuid`, pattern: `^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$`

  Saved API run UUID from POST /v1/digests; not the composition runId or email textHash.

  **Example:**

  ```json
  "11111111-1111-4111-8111-111111111111"
  ```

#### Responses

##### 200 Saved outcomes for this edition.

**Content type:** `application/json`

[DeliveryList](#scalar-schema-deliverylist)

**Example:**

```json
{
  "recipients": [
    {
      "recipient": "reader@example.com",
      "attemptId": "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
      "status": "accepted",
      "providerId": "22222222-2222-4222-8222-222222222222",
      "suppressed": true,
      "requests": 0,
      "code": null
    }
  ]
}
```

##### 400, 404, 409, 413, 500

- `400` An email ledger validation/read error.
- `404` RUN_NOT_FOUND: no run with this UUID.
- `409` DIGEST_NOT_READY: generation has not completed successfully.
- `413` BODY_TOO_LARGE: request body exceeds 256 KiB.
- `500` Server/storage failure, e.g. OPERATION_FAILED. Inspect the sanitized server log; do not automatically repeat a paid operation.

**Content type:** `application/json`

[Error](#scalar-schema-error)

[Generated example](#scalar-example-3)

##### 401 UNAUTHORIZED: missing or invalid Bearer token.

**Headers:**

- **`WWW-Authenticate`**

  `string`

  **Example:**

  ```json
  "Bearer"
  ```

**Content type:** `application/json`

[Error](#scalar-schema-error)

[Generated example](#scalar-example-3)

## Schemas

<a id="scalar-schema-error"></a>

### Error

**Type:** `object`, no additional properties

- **`error` (required)**: `object`, no additional properties
  - **`code` (required)**: `string`

    Stable machine-readable error code. Treat unrecognized codes conservatively.

<a id="scalar-example-7"></a>

**Generated example:**

```json
{
  "error": {
    "code": ""
  }
}
```

<a id="scalar-schema-runwindow"></a>

### RunWindow

**Type:** `object`, no additional properties

Collection interval \[from, to): from is inclusive, to is exclusive. Both must be valid UTC calendar timestamps and from must precede to. The fixed interval is saved with the run.

- **`from` (required)**: `string`, format: `date-time`, pattern: `^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{1,3})?Z$`

  ISO 8601 UTC timestamp ending in Z; responses use millisecond precision.
- **`to` (required)**: `string`, format: `date-time`, pattern: `^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{1,3})?Z$`

  ISO 8601 UTC timestamp ending in Z; responses use millisecond precision.

**Example:**

```json
{
  "from": "2026-10-27T13:00:00.000Z",
  "to": "2026-10-28T13:00:00.000Z"
}
```

<a id="scalar-schema-searchcompany"></a>

### SearchCompany

**Type:** `object`, no additional properties

- **`id` (required)**: `string`, minLength: `1`, maxLength: `80`, pattern: `^[A-Za-z0-9][A-Za-z0-9_-]*$`
- **`name` (required)**: `string`, minLength: `1`, maxLength: `120`

  Nonempty text; control characters are rejected. Configuration text is trimmed.

<a id="scalar-example-8"></a>

**Generated example:**

```json
{
  "id": "",
  "name": ""
}
```

<a id="scalar-schema-searchtopic"></a>

### SearchTopic

**Type:** `object`, no additional properties

Exactly one topic per section. Vocabulary values are unique case-insensitively; requiredAnchors must belong to allowedAnchors. Company IDs/names must be unique case-insensitively; names must belong to allowedAnchors and cover requiredAnchors. Companies are not permitted for market. companies and searchQueries are mutually exclusive. Without either, legacy search compiles phrases using the configured vocabulary. Additional search-specific checks occur before collection.

**All of:**

- **Not:**

  `object`

  **Required fields:** `companies, searchQueries`
- `any`

* **`allowedAnchors` (required)**: `array`, minItems: `1`, maxItems: `30`, uniqueItems: `true`

  **Items:**

  `string`, minLength: `1`, maxLength: `150`

  Nonempty text; control characters are rejected. Configuration text is trimmed.
* **`allowedQualifiers` (required)**: `array`, minItems: `1`, maxItems: `30`, uniqueItems: `true`

  **Items:**

  `string`, minLength: `1`, maxLength: `150`

  Nonempty text; control characters are rejected. Configuration text is trimmed.
* **`description` (required)**: `string`, minLength: `1`, maxLength: `4000`

  Nonempty text; control characters are rejected. Configuration text is trimmed.
* **`id` (required)**: `string`, possible values: `"rezolve", "competitors", "market"`
* **`requiredAnchors` (required)**: `array`, minItems: `0`, maxItems: `5`, uniqueItems: `true`

  **Items:**

  `string`, minLength: `1`, maxLength: `150`

  Nonempty text; control characters are rejected. Configuration text is trimmed.
* **`title` (required)**: `string`, minLength: `1`, maxLength: `500`

  Nonempty text; control characters are rejected. Configuration text is trimmed.
* **`companies`**: array of [SearchCompany](#scalar-schema-searchcompany), minItems: `1`, maxItems: `20`
* **`searchQueries`**: `array`, minItems: `1`, maxItems: `5`, uniqueItems: `true`

  Exact search phrases: 2–7 words, normalized whitespace, no dates, relative-time words, quotes, Boolean operators, colons or parentheses. Phrases must also be unique after case/whitespace normalization.

  **Items:**

  `string`, minLength: `1`, maxLength: `150`

  Nonempty text; control characters are rejected. Configuration text is trimmed.

<a id="scalar-example-9"></a>

**Generated example:**

```json
{
  "id": "rezolve",
  "title": "",
  "description": "",
  "allowedAnchors": [
    ""
  ],
  "allowedQualifiers": [
    ""
  ],
  "requiredAnchors": [
    ""
  ],
  "companies": [
    {
      "id": "",
      "name": ""
    }
  ],
  "searchQueries": [
    ""
  ]
}
```

<a id="scalar-schema-rsssource"></a>

### RssSource

**Type:** `object`, no additional properties

- **`id` (required)**: `string`, minLength: `1`, maxLength: `80`, pattern: `^[A-Za-z0-9][A-Za-z0-9_-]*$`
- **`section` (required)**: `string`, possible values: `"rezolve", "competitors", "market"`
- **`url` (required)**: `string`, format: `uri`, maxLength: `2048`, pattern: `^https?://`

  Public HTTP(S) URL without embedded credentials or secret query parameters.

<a id="scalar-example-10"></a>

**Generated example:**

```json
{
  "id": "",
  "url": "",
  "section": "rezolve"
}
```

<a id="scalar-schema-signalssource"></a>

### SignalsSource

**Type:** `object`, no additional properties

References an existing external Signals list. Updating listId switches the list used by collection; this API does not edit list membership.

- **`id` (required)**: `string`, minLength: `1`, maxLength: `80`, pattern: `^[A-Za-z0-9][A-Za-z0-9_-]*$`
- **`listId` (required)**: `string`, minLength: `1`, maxLength: `80`, pattern: `^[A-Za-z0-9][A-Za-z0-9_-]*$`
- **`section` (required)**: `string`, possible values: `"rezolve", "competitors", "market"`

<a id="scalar-example-11"></a>

**Generated example:**

```json
{
  "id": "",
  "listId": "",
  "section": "rezolve"
}
```

<a id="scalar-schema-serpersettings"></a>

### SerperSettings

**Type:** `object`, no additional properties

- **`articleTimeoutMs` (required)**: `integer`, minimum: `100`, maximum: `30000`
- **`maxArticleBytes` (required)**: `integer`, minimum: `64000`, maximum: `2000000`
- **`maxArticleRequests` (required)**: `integer`, minimum: `0`, maximum: `100`
- **`maxNewsRequests` (required)**: `integer`, minimum: `0`, maximum: `20`
- **`maxResults` (required)**: `integer`, minimum: `1`, maximum: `10`
- **`usdPerCredit` (required)**: `number`, minimum: `0`, maximum: `1`

<a id="scalar-example-12"></a>

**Generated example:**

```json
{
  "maxNewsRequests": 0,
  "maxArticleRequests": 0,
  "maxResults": 1,
  "articleTimeoutMs": 100,
  "maxArticleBytes": 64000,
  "usdPerCredit": 0
}
```

<a id="scalar-schema-collectionlimits"></a>

### CollectionLimits

**Type:** `object`, no additional properties

- **`maxLlmRequests` (required)**: `integer`, minimum: `0`, maximum: `40`
- **`maxOutputTokens` (required)**: `integer`, minimum: `100`, maximum: `4000`
- **`maxSearchRequests` (required)**: `integer`, minimum: `0`, maximum: `20`
- **`maxSearchResults` (required)**: `integer`, minimum: `1`, maximum: `20`
- **`timeoutMs` (required)**: `integer`, minimum: `100`, maximum: `120000`

<a id="scalar-example-13"></a>

**Generated example:**

```json
{
  "timeoutMs": 100,
  "maxSearchRequests": 0,
  "maxLlmRequests": 0,
  "maxOutputTokens": 100,
  "maxSearchResults": 1
}
```

<a id="scalar-schema-appconfig"></a>

### AppConfig

**Type:** `object`, no additional properties

Complete non-secret configuration, not a patch. timezone must be supported by Intl.DateTimeFormat (normally an IANA timezone). topics must contain rezolve, competitors and market exactly once. Source IDs must be unique across rss and signals. Unknown fields and values containing active credentials are rejected. Credentials are managed outside this API.

- **`limits` (required)**: [CollectionLimits](#scalar-schema-collectionlimits)
- **`maxPreviewItems` (required)**: `integer`, minimum: `1`, maximum: `50`
- **`name` (required)**: `string`, minLength: `1`, maxLength: `500`

  Nonempty text; control characters are rejected. Configuration text is trimmed.
- **`rss` (required)**: array of [RssSource](#scalar-schema-rsssource), maxItems: `10`
- **`sendTime` (required)**: `string`, pattern: `^(?:[01]\d|2[0-3]):[0-5]\d$`

  HH:mm local time. This configuration field does not enable or reconfigure the worker schedule; scheduling is managed separately by the operator CLI.
- **`signals` (required)**: array of [SignalsSource](#scalar-schema-signalssource), maxItems: `10`
- **`timezone` (required)**: `string`, minLength: `1`, maxLength: `100`

  Nonempty text; control characters are rejected. Configuration text is trimmed.
- **`topics` (required)**: array of [SearchTopic](#scalar-schema-searchtopic), minItems: `3`, maxItems: `3`
- **`version` (required)**: `integer`, const: `1`
- **`serper`**: [SerperSettings](#scalar-schema-serpersettings)

**Example:**

```json
{
  "version": 1,
  "name": "Example Brief",
  "timezone": "America/Los_Angeles",
  "sendTime": "06:00",
  "maxPreviewItems": 8,
  "topics": [
    {
      "id": "rezolve",
      "title": "Company news",
      "description": "Example company announcements.",
      "allowedAnchors": [
        "Example Commerce"
      ],
      "allowedQualifiers": [
        "news"
      ],
      "requiredAnchors": [
        "Example Commerce"
      ],
      "companies": [
        {
          "id": "example-commerce",
          "name": "Example Commerce"
        }
      ]
    },
    {
      "id": "competitors",
      "title": "Competitor news",
      "description": "Example competitor announcements.",
      "allowedAnchors": [
        "Example Search"
      ],
      "allowedQualifiers": [
        "news"
      ],
      "requiredAnchors": [
        "Example Search"
      ],
      "companies": [
        {
          "id": "example-search",
          "name": "Example Search"
        }
      ]
    },
    {
      "id": "market",
      "title": "Market news",
      "description": "Retail technology announcements.",
      "allowedAnchors": [
        "retail technology"
      ],
      "allowedQualifiers": [
        "news"
      ],
      "requiredAnchors": [
        "retail technology"
      ],
      "searchQueries": [
        "retail technology news"
      ]
    }
  ],
  "rss": [],
  "signals": [],
  "limits": {
    "timeoutMs": 30000,
    "maxSearchRequests": 3,
    "maxLlmRequests": 2,
    "maxOutputTokens": 1200,
    "maxSearchResults": 10
  }
}
```

<a id="scalar-schema-storedconfig"></a>

### StoredConfig

**Type:** `object`, no additional properties

- **`config` (required)**: [AppConfig](#scalar-schema-appconfig)

  Complete non-secret configuration, not a patch. timezone must be supported by Intl.DateTimeFormat (normally an IANA timezone). topics must contain rezolve, competitors and market exactly once. Source IDs must be unique across rss and signals. Unknown fields and values containing active credentials are rejected. Credentials are managed outside this API.
- **`version` (required)**: `string`, pattern: `^[a-f0-9]{64}$`

  Lowercase SHA-256 hex.

**Example:**

```json
{
  "config": {
    "version": 1,
    "name": "Example Brief",
    "timezone": "America/Los_Angeles",
    "sendTime": "06:00",
    "maxPreviewItems": 8,
    "topics": [
      {
        "id": "rezolve",
        "title": "Company news",
        "description": "Example company announcements.",
        "allowedAnchors": [
          "Example Commerce"
        ],
        "allowedQualifiers": [
          "news"
        ],
        "requiredAnchors": [
          "Example Commerce"
        ],
        "companies": [
          {
            "id": "example-commerce",
            "name": "Example Commerce"
          }
        ]
      },
      {
        "id": "competitors",
        "title": "Competitor news",
        "description": "Example competitor announcements.",
        "allowedAnchors": [
          "Example Search"
        ],
        "allowedQualifiers": [
          "news"
        ],
        "requiredAnchors": [
          "Example Search"
        ],
        "companies": [
          {
            "id": "example-search",
            "name": "Example Search"
          }
        ]
      },
      {
        "id": "market",
        "title": "Market news",
        "description": "Retail technology announcements.",
        "allowedAnchors": [
          "retail technology"
        ],
        "allowedQualifiers": [
          "news"
        ],
        "requiredAnchors": [
          "retail technology"
        ],
        "searchQueries": [
          "retail technology news"
        ]
      }
    ],
    "rss": [],
    "signals": [],
    "limits": {
      "timeoutMs": 30000,
      "maxSearchRequests": 3,
      "maxLlmRequests": 2,
      "maxOutputTokens": 1200,
      "maxSearchResults": 10
    }
  },
  "version": "9dcd53d4b3a53977e6dcc7c6a93ff8238ed6bf38c18b70f0900eeaeb1ac0b5c7"
}
```

<a id="scalar-schema-generateinput"></a>

### GenerateInput

**Type:** `object`, no additional properties

Use {} for the default sources/sections and the preceding 24 hours fixed at the first request. RSS is available only when explicitly requested. A new idempotency key can incur new provider charges. Generation never sends email.

- **`sections`**: `array`, default: `["rezolve","competitors","market"]`, minItems: `1`, maxItems: `3`, uniqueItems: `true`

  **Items:**

  `string`, possible values: `"rezolve", "competitors", "market"`
- **`sources`**: `array`, default: `["perplexity","serper","signals"]`, minItems: `1`, maxItems: `4`, uniqueItems: `true`

  **Items:**

  `string`, possible values: `"perplexity", "serper", "signals", "rss"`
- **`window`**: [RunWindow](#scalar-schema-runwindow)

  Collection interval \[from, to): from is inclusive, to is exclusive. Both must be valid UTC calendar timestamps and from must precede to. The fixed interval is saved with the run.

<a id="scalar-example-14"></a>

**Generated example:**

```json
{
  "window": {
    "from": "2026-10-27T13:00:00.000Z",
    "to": "2026-10-28T13:00:00.000Z"
  },
  "sources": [
    "perplexity",
    "serper",
    "signals"
  ],
  "sections": [
    "rezolve",
    "competitors",
    "market"
  ]
}
```

<a id="scalar-schema-runsummary"></a>

### RunSummary

**Type:** `object`, no additional properties

costUsd=null means at least part of the final cost is unknown; knownCostUsd is the known subtotal. ready/draft are not editorial approval.

- **`costUsd` (required)**: `number | null`, minimum: `0`
- **`digestStatus` (required)**: `string`, possible values: `"draft", "quiet_day", "incomplete", "held"`
- **`knownCostUsd` (required)**: `number`, minimum: `0`
- **`requestCount` (required)**: `integer`, minimum: `0`
- **`stories` (required)**: `integer`, minimum: `0`, maximum: `8`
- **`diagnostics`**: `array`, maxItems: `100`

  **Items:**

  `string`, pattern: `^[A-Za-z0-9_-]{1,80}$`

**Example:**

```json
{
  "digestStatus": "draft",
  "stories": 4,
  "requestCount": 10,
  "costUsd": 0.1,
  "knownCostUsd": 0.1,
  "diagnostics": []
}
```

<a id="scalar-schema-runlinks"></a>

### RunLinks

**Type:** `object`, no additional properties

- **`delivery` (required)**: `string`
- **`html` (required)**: `string`
- **`json` (required)**: `string`
- **`self` (required)**: `string`
- **`text` (required)**: `string`

<a id="scalar-example-15"></a>

**Generated example:**

```json
{
  "self": "",
  "json": "",
  "html": "",
  "text": "",
  "delivery": ""
}
```

<a id="scalar-schema-digestrun"></a>

### DigestRun

**Type:** `object`, no additional properties

running includes waiting for a PostgreSQL worker. Poll the self link until ready, failed or interrupted. ready means persisted output exists, not editorial approval; inspect result.digestStatus and the edition. failed/interrupted are terminal for this ID. There is no automatic rerun of claimed paid work. A repeated idempotency key returns this run and its saved configuration/window.

- **`configVersion` (required)**: `string`, pattern: `^[a-f0-9]{64}$`

  Lowercase SHA-256 hex.
- **`createdAt` (required)**: `string`, format: `date-time`, pattern: `^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{1,3})?Z$`

  ISO 8601 UTC timestamp ending in Z; responses use millisecond precision.
- **`errorCode` (required)**: `string | null`
- **`id` (required)**: `string`, format: `uuid`, pattern: `^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$`
- **`links` (required)**: [RunLinks](#scalar-schema-runlinks)
- **`result` (required)**: [RunSummary](#scalar-schema-runsummary) | `null`

  costUsd=null means at least part of the final cost is unknown; knownCostUsd is the known subtotal. ready/draft are not editorial approval.
- **`sections` (required)**: `array`

  **Items:**

  `string`, possible values: `"rezolve", "competitors", "market"`
- **`sources` (required)**: `array`

  **Items:**

  `string`, possible values: `"perplexity", "serper", "signals", "rss"`
- **`status` (required)**: `string`, possible values: `"running", "ready", "failed", "interrupted"`
- **`updatedAt` (required)**: `string`, format: `date-time`, pattern: `^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{1,3})?Z$`

  ISO 8601 UTC timestamp ending in Z; responses use millisecond precision.
- **`window` (required)**: [RunWindow](#scalar-schema-runwindow)

  Collection interval \[from, to): from is inclusive, to is exclusive. Both must be valid UTC calendar timestamps and from must precede to. The fixed interval is saved with the run.

**Example:**

```json
{
  "id": "11111111-1111-4111-8111-111111111111",
  "status": "running",
  "createdAt": "2026-10-28T13:00:00.000Z",
  "updatedAt": "2026-10-28T13:00:00.000Z",
  "configVersion": "9dcd53d4b3a53977e6dcc7c6a93ff8238ed6bf38c18b70f0900eeaeb1ac0b5c7",
  "window": {
    "from": "2026-10-27T13:00:00.000Z",
    "to": "2026-10-28T13:00:00.000Z"
  },
  "sources": [
    "perplexity",
    "serper",
    "signals"
  ],
  "sections": [
    "rezolve",
    "competitors",
    "market"
  ],
  "result": null,
  "errorCode": null,
  "links": {
    "self": "/v1/digests/11111111-1111-4111-8111-111111111111",
    "json": "/v1/digests/11111111-1111-4111-8111-111111111111/json",
    "html": "/v1/digests/11111111-1111-4111-8111-111111111111/html",
    "text": "/v1/digests/11111111-1111-4111-8111-111111111111/text",
    "delivery": "/v1/digests/11111111-1111-4111-8111-111111111111/delivery"
  }
}
```

<a id="scalar-schema-digestrunlist"></a>

### DigestRunList

**Type:** `object`, no additional properties

- **`items` (required)**: array of [DigestRun](#scalar-schema-digestrun), maxItems: `100`

**Example:**

```json
{
  "items": []
}
```

<a id="scalar-schema-providerusage"></a>

### ProviderUsage

**Type:** `object`, no additional properties

USD values. null cost means unknown; estimatedCostUsd is the part calculated from published/configured rates, not a provider-confirmed invoice. A known subtotal may coexist with an unknown total.

- **`costUsd` (required)**: `number | null`, minimum: `0`
- **`inputTokens` (required)**: `integer | null`, minimum: `0`
- **`outputTokens` (required)**: `integer | null`, minimum: `0`
- **`requestCount` (required)**: `integer`, minimum: `0`
- **`estimatedCostUsd`**: `number`, minimum: `0`
- **`knownCostUsd`**: `number`, minimum: `0`

<a id="scalar-example-16"></a>

**Generated example:**

```json
{
  "requestCount": 0,
  "inputTokens": 0,
  "outputTokens": 0,
  "costUsd": 0,
  "knownCostUsd": 0,
  "estimatedCostUsd": 0
}
```

<a id="scalar-schema-diagnostic"></a>

### Diagnostic

**Type:** `object`, no additional properties

- **`code` (required)**: `string`
- **`message` (required)**: `string`

<a id="scalar-example-17"></a>

**Generated example:**

```json
{
  "code": "",
  "message": ""
}
```

<a id="scalar-schema-publicationdate"></a>

### PublicationDate

**Type:** `object`, no additional properties

instant uses an ISO timestamp; day is YYYY-MM-DD without a known publication time; unknown has value=null. Publication metadata alone does not prove the event happened in the interval.

- **`precision` (required)**: `string`, possible values: `"instant", "day", "unknown"`
- **`value` (required)**: `string | null`

<a id="scalar-example-18"></a>

**Generated example:**

```json
{
  "value": "",
  "precision": "instant"
}
```

<a id="scalar-schema-coveragesource"></a>

### CoverageSource

**Type:** `object`, no additional properties

- **`diagnostics` (required)**: array of [Diagnostic](#scalar-schema-diagnostic)
- **`elapsedMs` (required)**: `number`, minimum: `0`
- **`provider` (required)**: `string`, possible values: `"perplexity", "serper", "signals", "rss"`
- **`section` (required)**: `string`, possible values: `"rezolve", "competitors", "market"`
- **`sourceId` (required)**: `string`
- **`status` (required)**: `string`, possible values: `"ok", "empty", "partial", "unavailable", "failed"`
- **`usage` (required)**: [ProviderUsage](#scalar-schema-providerusage)

  USD values. null cost means unknown; estimatedCostUsd is the part calculated from published/configured rates, not a provider-confirmed invoice. A known subtotal may coexist with an unknown total.

<a id="scalar-example-19"></a>

**Generated example:**

```json
{
  "sourceId": "",
  "provider": "perplexity",
  "section": "rezolve",
  "status": "ok",
  "diagnostics": [
    {
      "code": "",
      "message": ""
    }
  ],
  "usage": {
    "requestCount": 0,
    "inputTokens": 0,
    "outputTokens": 0,
    "costUsd": 0,
    "knownCostUsd": 0,
    "estimatedCostUsd": 0
  },
  "elapsedMs": 0
}
```

<a id="scalar-schema-collectiontotals"></a>

### CollectionTotals

**Type:** `object`, no additional properties

- **`collectedItems` (required)**: `integer`, minimum: `0`
- **`costUsd` (required)**: `number | null`, minimum: `0`
- **`displayedItems` (required)**: `integer`, minimum: `0`
- **`estimatedCostUsd` (required)**: `number`, minimum: `0`
- **`knownCostUsd` (required)**: `number`, minimum: `0`
- **`sourceRequests` (required)**: `integer`, minimum: `0`
- **`unknownCostSources` (required)**: `integer`, minimum: `0`

<a id="scalar-example-20"></a>

**Generated example:**

```json
{
  "collectedItems": 0,
  "displayedItems": 0,
  "sourceRequests": 0,
  "knownCostUsd": 0,
  "estimatedCostUsd": 0,
  "costUsd": 0,
  "unknownCostSources": 0
}
```

<a id="scalar-schema-supportedtext"></a>

### SupportedText

**Type:** `object`, no additional properties

Evidence IDs reference this story’s evidence array only; they are not an independent guarantee of factual accuracy.

- **`evidenceIds` (required)**: `array`

  **Items:**

  `string`, pattern: `^e[1-9][0-9]*$`
- **`text` (required)**: `string`

<a id="scalar-example-21"></a>

**Generated example:**

```json
{
  "text": "",
  "evidenceIds": [
    ""
  ]
}
```

<a id="scalar-schema-storyevidence"></a>

### StoryEvidence

**Type:** `object`, no additional properties

- **`candidateId` (required)**: `string`
- **`field` (required)**: `string`, possible values: `"title", "snippet"`
- **`id` (required)**: `string`
- **`occurrenceId` (required)**: `string`
- **`publishedAt` (required)**: [PublicationDate](#scalar-schema-publicationdate)

  instant uses an ISO timestamp; day is YYYY-MM-DD without a known publication time; unknown has value=null. Publication metadata alone does not prove the event happened in the interval.
- **`publisher` (required)**: `string | null`
- **`quote` (required)**: `string`
- **`url` (required)**: `string`, format: `uri`

<a id="scalar-example-22"></a>

**Generated example:**

```json
{
  "id": "",
  "candidateId": "",
  "occurrenceId": "",
  "field": "title",
  "quote": "",
  "url": "",
  "publishedAt": {
    "value": "",
    "precision": "instant"
  },
  "publisher": ""
}
```

<a id="scalar-schema-digeststory"></a>

### DigestStory

**Type:** `object`, no additional properties

- **`dateReviewRequired` (required)**: `boolean`
- **`evidence` (required)**: array of [StoryEvidence](#scalar-schema-storyevidence)
- **`headline` (required)**: [SupportedText](#scalar-schema-supportedtext)

  Evidence IDs reference this story’s evidence array only; they are not an independent guarantee of factual accuracy.
- **`kind` (required)**: `string`, possible values: `"new", "update"`
- **`publicationDate` (required)**: [PublicationDate](#scalar-schema-publicationdate)

  instant uses an ISO timestamp; day is YYYY-MM-DD without a known publication time; unknown has value=null. Publication metadata alone does not prove the event happened in the interval.
- **`section` (required)**: `string`, possible values: `"rezolve", "competitors", "market"`
- **`sourceKind` (required)**: `string`, possible values: `"company_release", "industry_press", "financial_press", "social_post", "other"`
- **`sourceTitle` (required)**: `string`
- **`storyId` (required)**: `string`
- **`summary` (required)**: [SupportedText](#scalar-schema-supportedtext)

  Evidence IDs reference this story’s evidence array only; they are not an independent guarantee of factual accuracy.
- **`updateOf` (required)**: `string | null`
- **`url` (required)**: `string`, format: `uri`
- **`whyItMatters` (required)**: `string`

  Editorial interpretation, not an additional source claim.

<a id="scalar-example-23"></a>

**Generated example:**

```json
{
  "storyId": "",
  "headline": {
    "text": "",
    "evidenceIds": [
      ""
    ]
  },
  "summary": {
    "text": "",
    "evidenceIds": [
      ""
    ]
  },
  "whyItMatters": "",
  "section": "rezolve",
  "kind": "new",
  "updateOf": "",
  "sourceKind": "company_release",
  "url": "",
  "sourceTitle": "",
  "publicationDate": {
    "value": "",
    "precision": "instant"
  },
  "dateReviewRequired": true,
  "evidence": [
    {
      "id": "",
      "candidateId": "",
      "occurrenceId": "",
      "field": "title",
      "quote": "",
      "url": "",
      "publishedAt": {
        "value": "",
        "precision": "instant"
      },
      "publisher": ""
    }
  ]
}
```

<a id="scalar-schema-digestsection"></a>

### DigestSection

**Type:** `object`, no additional properties

- **`id` (required)**: `string`, possible values: `"rezolve", "competitors", "market"`
- **`items` (required)**: array of [DigestStory](#scalar-schema-digeststory), maxItems: `8`
- **`title` (required)**: `string`

<a id="scalar-example-24"></a>

**Generated example:**

```json
{
  "id": "rezolve",
  "title": "",
  "items": [
    {
      "storyId": "",
      "headline": {
        "text": "",
        "evidenceIds": [
          ""
        ]
      },
      "summary": {
        "text": "",
        "evidenceIds": [
          ""
        ]
      },
      "whyItMatters": "",
      "section": "rezolve",
      "kind": "new",
      "updateOf": "",
      "sourceKind": "company_release",
      "url": "",
      "sourceTitle": "",
      "publicationDate": {
        "value": "",
        "precision": "instant"
      },
      "dateReviewRequired": true,
      "evidence": [
        {
          "id": "",
          "candidateId": "",
          "occurrenceId": "",
          "field": "title",
          "quote": "",
          "url": "",
          "publishedAt": {
            "value": "",
            "precision": "instant"
          },
          "publisher": ""
        }
      ]
    }
  ]
}
```

<a id="scalar-schema-searchrequest"></a>

### SearchRequest

**Type:** `object`, no additional properties

Recorded collection request attempts, including unsuccessful attempts. Missing queries are not regenerated. This is historical inspection; reading the endpoint makes no provider request.

- **`provider` (required)**: `string`, possible values: `"perplexity", "serper"`
- **`queries` (required)**: `(array of string) | null`
- **`recordingStatus` (required)**: `string`, possible values: `"recorded", "not-recorded"`
- **`section` (required)**: `string`, possible values: `"rezolve", "competitors", "market"`
- **`sourceId` (required)**: `string`

<a id="scalar-example-25"></a>

**Generated example:**

```json
{
  "provider": "perplexity",
  "sourceId": "",
  "section": "rezolve",
  "recordingStatus": "recorded",
  "queries": [
    ""
  ]
}
```

<a id="scalar-schema-serperdiscovery"></a>

### SerperDiscovery

**Type:** `object`, no additional properties

- **`linkedExistingUrl` (required)**: `string | null`
- **`providerDate` (required)**: `string | null`
- **`publishedAt` (required)**: [PublicationDate](#scalar-schema-publicationdate)

  instant uses an ISO timestamp; day is YYYY-MM-DD without a known publication time; unknown has value=null. Publication metadata alone does not prove the event happened in the interval.
- **`reason` (required)**: `string`
- **`status` (required)**: `string`, possible values: `"accepted", "duplicate", "outside_window", "rejected", "unrecorded"`
- **`title` (required)**: `string | null`
- **`url` (required)**: `string | null`

<a id="scalar-example-26"></a>

**Generated example:**

```json
{
  "url": "",
  "title": "",
  "providerDate": "",
  "publishedAt": {
    "value": "",
    "precision": "instant"
  },
  "status": "accepted",
  "reason": "",
  "linkedExistingUrl": ""
}
```

<a id="scalar-schema-serperdiscoverysource"></a>

### SerperDiscoverySource

**Type:** `object`, no additional properties

- **`articleCacheHits` (required)**: `integer | null`, minimum: `0`
- **`articleRequests` (required)**: `integer | null`, minimum: `0`
- **`costUsd` (required)**: `number | null`, minimum: `0`
- **`credits` (required)**: `number | null`, minimum: `0`
- **`discoveries` (required)**: array of [SerperDiscovery](#scalar-schema-serperdiscovery)
- **`estimatedCostUsd` (required)**: `number`, minimum: `0`
- **`searchRequests` (required)**: `integer | null`, minimum: `0`
- **`section` (required)**: `string`, possible values: `"rezolve", "competitors", "market"`
- **`sourceId` (required)**: `string`

<a id="scalar-example-27"></a>

**Generated example:**

```json
{
  "sourceId": "",
  "section": "rezolve",
  "searchRequests": 0,
  "articleRequests": 0,
  "articleCacheHits": 0,
  "credits": 0,
  "costUsd": 0,
  "estimatedCostUsd": 0,
  "discoveries": [
    {
      "url": "",
      "title": "",
      "providerDate": "",
      "publishedAt": {
        "value": "",
        "precision": "instant"
      },
      "status": "accepted",
      "reason": "",
      "linkedExistingUrl": ""
    }
  ]
}
```

<a id="scalar-schema-rendereddigest"></a>

### RenderedDigest

**Type:** `object`, no additional properties

Structured saved edition with evidence, historical source/query inspection and diagnostics. This is not the complete internal composition bundle. A held edition has no publishable stories; its JSON remains inspectable. The final reader-facing email is returned by /html and /text. All editions require editorial review; the automated validator is not implemented yet.

- **`bodyText` (required)**: `string`
- **`collectionTotals` (required)**: [CollectionTotals](#scalar-schema-collectiontotals)
- **`composerVersion` (required)**: `string`, const: `"r1-compose-1"`
- **`configHash` (required)**: `string`, pattern: `^[a-f0-9]{64}$`

  Lowercase SHA-256 hex.
- **`coverage` (required)**: array of [CoverageSource](#scalar-schema-coveragesource)
- **`coverageIncomplete` (required)**: `boolean`
- **`createdAt` (required)**: `string`, format: `date-time`, pattern: `^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{1,3})?Z$`

  ISO 8601 UTC timestamp ending in Z; responses use millisecond precision.
- **`diagnostics` (required)**: `array of string`
- **`kind` (required)**: `string`, const: `"composed-digest"`
- **`language` (required)**: `string`, const: `"en"`
- **`mode` (required)**: `string`, possible values: `"live", "offline", "replay"`
- **`originalComposition` (required)**: `object`, no additional properties
  - **`runId` (required)**: `string`
  - **`usage` (required)**: [ProviderUsage](#scalar-schema-providerusage)

    USD values. null cost means unknown; estimatedCostUsd is the part calculated from published/configured rates, not a provider-confirmed invoice. A known subtotal may coexist with an unknown total.
- **`originalSelection` (required)**: `object`, no additional properties
  - **`runId` (required)**: `string`
  - **`usage` (required)**: [ProviderUsage](#scalar-schema-providerusage)

    USD values. null cost means unknown; estimatedCostUsd is the part calculated from published/configured rates, not a provider-confirmed invoice. A known subtotal may coexist with an unknown total.
- **`originRunId` (required)**: `string`
- **`reviewStatus` (required)**: `string`, const: `"unreviewed"`
- **`runId` (required)**: `string`

  Composition artifact ID; different from the UUID used by /v1/digests/{id}.
- **`searchRequests` (required)**: array of [SearchRequest](#scalar-schema-searchrequest)
- **`searchRequestsScope` (required)**: `string`, possible values: `"collection", "original-collection"`
- **`sections` (required)**: array of [DigestSection](#scalar-schema-digestsection), minItems: `3`, maxItems: `3`
- **`selectionHash` (required)**: `string`, pattern: `^[a-f0-9]{64}$`

  Lowercase SHA-256 hex.
- **`selectionRunId` (required)**: `string`
- **`serperDiscoverySources` (required)**: array of [SerperDiscoverySource](#scalar-schema-serperdiscoverysource)
- **`sourceHashEncoding` (required)**: `string`, const: `"canonical-json-v1"`
- **`sourceSnapshotHash` (required)**: `string`, pattern: `^[a-f0-9]{64}$`

  Lowercase SHA-256 hex.
- **`status` (required)**: `string`, possible values: `"draft", "quiet_day", "incomplete", "held"`
- **`textHash` (required)**: `string`, pattern: `^[a-f0-9]{64}$`

  Lowercase SHA-256 hex.
- **`textHashAlgorithm` (required)**: `string`, const: `"sha256"`
- **`textVersion` (required)**: `string`, const: `"r1-digest-text-1"`
- **`usage` (required)**: [ProviderUsage](#scalar-schema-providerusage)

  USD values. null cost means unknown; estimatedCostUsd is the part calculated from published/configured rates, not a provider-confirmed invoice. A known subtotal may coexist with an unknown total.
- **`version` (required)**: `integer`, const: `1`
- **`window` (required)**: [RunWindow](#scalar-schema-runwindow)

  Collection interval \[from, to): from is inclusive, to is exclusive. Both must be valid UTC calendar timestamps and from must precede to. The fixed interval is saved with the run.
- **`wordCount` (required)**: `integer`, minimum: `0`, maximum: `500`
- **`wordCountScope` (required)**: `string`, const: `"reader-edition-excluding-operator-appendix"`

<a id="scalar-example-28"></a>

**Generated example:**

```json
{
  "version": 1,
  "kind": "composed-digest",
  "composerVersion": "r1-compose-1",
  "textVersion": "r1-digest-text-1",
  "runId": "",
  "createdAt": "",
  "mode": "live",
  "status": "draft",
  "reviewStatus": "unreviewed",
  "language": "en",
  "window": {
    "from": "2026-10-27T13:00:00.000Z",
    "to": "2026-10-28T13:00:00.000Z"
  },
  "configHash": "",
  "sourceHashEncoding": "canonical-json-v1",
  "sourceSnapshotHash": "",
  "selectionHash": "",
  "selectionRunId": "",
  "originalSelection": {
    "runId": "",
    "usage": {
      "requestCount": 0,
      "inputTokens": 0,
      "outputTokens": 0,
      "costUsd": 0,
      "knownCostUsd": 0,
      "estimatedCostUsd": 0
    }
  },
  "originRunId": "",
  "usage": {
    "requestCount": 0,
    "inputTokens": 0,
    "outputTokens": 0,
    "costUsd": 0,
    "knownCostUsd": 0,
    "estimatedCostUsd": 0
  },
  "originalComposition": {
    "runId": "",
    "usage": {
      "requestCount": 0,
      "inputTokens": 0,
      "outputTokens": 0,
      "costUsd": 0,
      "knownCostUsd": 0,
      "estimatedCostUsd": 0
    }
  },
  "collectionTotals": {
    "collectedItems": 0,
    "displayedItems": 0,
    "sourceRequests": 0,
    "knownCostUsd": 0,
    "estimatedCostUsd": 0,
    "costUsd": 0,
    "unknownCostSources": 0
  },
  "coverageIncomplete": true,
  "coverage": [
    {
      "sourceId": "",
      "provider": "perplexity",
      "section": "rezolve",
      "status": "ok",
      "diagnostics": [
        {
          "code": "",
          "message": ""
        }
      ],
      "usage": {
        "requestCount": 0,
        "inputTokens": 0,
        "outputTokens": 0,
        "costUsd": 0,
        "knownCostUsd": 0,
        "estimatedCostUsd": 0
      },
      "elapsedMs": 0
    }
  ],
  "diagnostics": [
    ""
  ],
  "wordCount": 0,
  "wordCountScope": "reader-edition-excluding-operator-appendix",
  "textHashAlgorithm": "sha256",
  "textHash": "",
  "bodyText": "",
  "sections": [
    {
      "id": "rezolve",
      "title": "",
      "items": [
        {
          "storyId": "",
          "headline": {
            "text": "",
            "evidenceIds": [
              ""
            ]
          },
          "summary": {
            "text": "",
            "evidenceIds": [
              ""
            ]
          },
          "whyItMatters": "",
          "section": "rezolve",
          "kind": "new",
          "updateOf": "",
          "sourceKind": "company_release",
          "url": "",
          "sourceTitle": "",
          "publicationDate": {
            "value": "",
            "precision": "instant"
          },
          "dateReviewRequired": true,
          "evidence": [
            {
              "id": "",
              "candidateId": "",
              "occurrenceId": "",
              "field": "title",
              "quote": "",
              "url": "",
              "publishedAt": {
                "value": "",
                "precision": "instant"
              },
              "publisher": ""
            }
          ]
        }
      ]
    }
  ],
  "searchRequestsScope": "collection",
  "searchRequests": [
    {
      "provider": "perplexity",
      "sourceId": "",
      "section": "rezolve",
      "recordingStatus": "recorded",
      "queries": [
        ""
      ]
    }
  ],
  "serperDiscoverySources": [
    {
      "sourceId": "",
      "section": "rezolve",
      "searchRequests": 0,
      "articleRequests": 0,
      "articleCacheHits": 0,
      "credits": 0,
      "costUsd": 0,
      "estimatedCostUsd": 0,
      "discoveries": [
        {
          "url": "",
          "title": "",
          "providerDate": "",
          "publishedAt": {
            "value": "",
            "precision": "instant"
          },
          "status": "accepted",
          "reason": "",
          "linkedExistingUrl": ""
        }
      ]
    }
  ]
}
```

<a id="scalar-schema-sendinput"></a>

### SendInput

**Type:** `object`, no additional properties

Explicit recipients for this request. All addresses are validated before the first send; duplicates are rejected after lowercasing. Each recipient gets a separate email. The sender and reply-to come from server environment, not this body.

- **`recipients` (required)**: `array`, minItems: `1`, maxItems: `20`, uniqueItems: `true`

  **Items:**

  `string`, format: `email`, maxLength: `254`

  ASCII email address. Recipient matching is case-insensitive; surrounding whitespace is rejected.

<a id="scalar-example-29"></a>

**Generated example:**

```json
{
  "recipients": [
    ""
  ]
}
```

<a id="scalar-schema-deliveryrecovery"></a>

### DeliveryRecovery

**Type:** `object`, no additional properties

Provider receipt available when persisting the result failed. Reconcile manually before any retry; do not treat this as permission to send again.

- **`attemptId` (required)**: `string`, pattern: `^[a-f0-9]{64}$`

  Lowercase SHA-256 hex.
- **`bodyHash` (required)**: `string`, pattern: `^[a-f0-9]{64}$`

  Lowercase SHA-256 hex.
- **`providerId` (required)**: `string | null`, format: `uuid`
- **`requests` (required)**: `integer`, const: `1`
- **`status` (required)**: `string`, possible values: `"prepared", "sending", "accepted", "delivered", "failed", "unknown"`

<a id="scalar-example-30"></a>

**Generated example:**

```json
{
  "attemptId": "",
  "providerId": "",
  "status": "prepared",
  "requests": 1,
  "bodyHash": ""
}
```

<a id="scalar-schema-deliveryoutcome"></a>

### DeliveryOutcome

**Type:** `object`, no additional properties

Outcome per recipient. accepted means accepted by Resend, not delivered. unknown is ambiguous and must not be resent automatically. suppressed means this invocation did not repeat the reserved attempt. retryAt is a persisted cooldown, not an automatic retry schedule.

- **`attemptId` (required)**: `string`, pattern: `^[a-f0-9]{64}$`

  Lowercase SHA-256 hex.
- **`code` (required)**: `string | null`
- **`providerId` (required)**: `string | null`, format: `uuid`
- **`recipient` (required)**: `string`, format: `email`, maxLength: `254`

  ASCII email address. Recipient matching is case-insensitive; surrounding whitespace is rejected.
- **`requests` (required)**: `integer`, minimum: `0`
- **`status` (required)**: `string`, possible values: `"prepared", "sending", "accepted", "delivered", "failed", "unknown"`
- **`suppressed` (required)**: `boolean`
- **`recovery`**: [DeliveryRecovery](#scalar-schema-deliveryrecovery)

  Provider receipt available when persisting the result failed. Reconcile manually before any retry; do not treat this as permission to send again.
- **`retryAt`**: `string`, format: `date-time`, pattern: `^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{1,3})?Z$`

  ISO 8601 UTC timestamp ending in Z; responses use millisecond precision.

**Example:**

```json
{
  "recipient": "reader@example.com",
  "attemptId": "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
  "status": "accepted",
  "providerId": "22222222-2222-4222-8222-222222222222",
  "suppressed": false,
  "requests": 1,
  "code": null
}
```

<a id="scalar-schema-deliverylist"></a>

### DeliveryList

**Type:** `object`, no additional properties

- **`recipients` (required)**: array of [DeliveryOutcome](#scalar-schema-deliveryoutcome)

**Example:**

```json
{
  "recipients": [
    {
      "recipient": "reader@example.com",
      "attemptId": "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
      "status": "accepted",
      "providerId": "22222222-2222-4222-8222-222222222222",
      "suppressed": false,
      "requests": 1,
      "code": null
    }
  ]
}
```
