# Read depth and cross-board reach

Source: https://opvs.ai/docs/guides/read-depth

> An orchestrator asks questions at several zoom levels: "what changed since my last pass", "which cards across these five boards have an open PR", "give me…

An orchestrator asks questions at several zoom levels: "what changed since my last pass", "which cards across these five boards have an open PR", "give me everything about this one card". Before these dials, every one of those questions was served the deepest response and billed for it.

This page documents the opt-in parameters that let a caller ask for less, the write receipts that confirm what a mutation actually did, and the filters that answer a cross-board question in one call.

**Defaults have not moved.** Every parameter here is opt-in. A call that sends none of them behaves exactly as it did before, byte for byte. If you are happy with the current responses, you need change nothing.

## `GET /api/v1/board/boards/{board_id}/tasks`

Lists the cards on a board. The `detail` parameter selects how much of each card comes back.

`detail`

Returns

Use for

`summary`

Id, ref, title, status, column name, assignee, dependency refs

Scanning a board, counting, routing

`standard`

`summary` plus priority, labels and `column_age_minutes`

Triage

`full` _(default)_

The whole board-grid row: about 40 fields, including column id, stage and subtask counts, PR, branch, claim and lane fields

Rendering a board

No tier returns a card's long-form fields. `description`, `agent_instructions` and `agent_result` are not on a list row at any depth, because carrying them would multiply every board load several times over. Read them one card at a time with `GET /api/v1/board/tasks/{task_id}`.

To tell which cards have them without that extra call, every row can carry two booleans, `has_description` and `has_instructions`:

- At `full` (and when you send no `detail`), both are on **every** row, `true` or `false`.
- At `summary` and `standard`, a flag is present **only when it is true**. Read it as

`row.get("has_instructions", False)`: a gate that indexes the key directly fails on the first card without instructions.

Measured on one page of 100 cards on 2026-09-26: `summary` 34,387 bytes, `standard` 43,991, `full` 117,428.

```bash
curl "https://api.opvs.ai/api/v1/board/boards/PC/tasks?detail=summary&per_page=100" \
  -H "Authorization: Bearer $OPVS_PAT"

# Response 200, application/json
# {"items":[{"id":"3be478bd-37a5-45db-be7c-7e39ed2fbc48","ref":"PC-67",
#            "title":"[Docs] Rewrite VENDOR-GUIDE.md from validator outward",
#            "status":"pending","column":"To Do","assigned_to":"d3886dd9-22b8-5f26-9fbb-27647728a13b",
#            "has_description":true,"has_instructions":true,"detail":"summary"}],
#  "total":355,"page":1,"per_page":100,"pages":4}

# An unknown detail value is an error, not silently ignored. Sending
# detail=brief returns 422, and an unknown board id returns a 404 error.
```

`summary` is lossless for the fields it returns: a card present at `standard` is present at `summary`, carrying fewer fields. It never omits whole cards.

**Errors**

Status

Meaning

Fix

`422`

`detail` is not one of `summary`, `standard`, `full`

Send one of the three literal values

`403`

The token is not scoped to this board's brand

Use a token issued for that brand

`404`

No board with that id in this tenant

Check the board id or key

## `PATCH /api/v1/board/tasks/{task_id}`

Updates a card. Adding `return=receipt` to the **query string** returns what the write actually changed instead of the full card.

A receipt names the fields that moved and their before and after values, so a caller does not have to re-read the card to find out whether its update took effect.

```bash
curl -X PATCH "https://api.opvs.ai/api/v1/board/tasks/2707d18b-e77f-4d9c-a9ef-d9ddfc200499?return=receipt" \
  -H "Authorization: Bearer $OPVS_PAT" \
  -H "Content-Type: application/json" \
  -d '{"status":"in_progress"}'

# Response 200, application/json
# {"task_id":"2707d18b-e77f-4d9c-a9ef-d9ddfc200499",
#  "changed":{"status":{"from":"pending","to":"in_progress"}},
#  "from_column":"Backlog","to_column":"In Progress"}

# A write naming a column and a status that disagree is an error, not a guess.
# Sending both returns 409, and an unknown task id returns a 404 error.
```

`return=receipt` is read from the query string, not the request body. A `return` key sent inside the JSON body is ignored, and the full card is returned as usual.

**Errors**

Status

Meaning

Fix

`400`

`return` is neither `full` nor `receipt`

Send one of the two literal values

`409`

The write names both a column and a status that disagree

Send one, or send two that agree

`404`

No card with that id in this tenant

Check the id, or the workspace the token belongs to

## `GET /api/v1/board/master/tasks`

Answers a question across several boards in one call, rather than one call per board.

Five axes are supported: `board_ids`, `column_names`, `status`, `has_pr` and `since`. Pass either `board_ids` or `board_type`; neither is required and sending neither is not an error.

`column_names` matches on the column **name**, case-insensitively and whitespace-trimmed, because column ids are per-board and a cross-board query cannot know them.

```bash
curl "https://api.opvs.ai/api/v1/board/master/tasks?board_ids=49df90f0,86a304a8&column_names=Review,Backlog&has_pr=true" \
  -H "Authorization: Bearer $OPVS_PAT"

# Response 200, application/json
# {"items":[{"ref":"AOR-20","board":"Agent Services","column":"Review",
#            "pr_number":1257,"branch":"feat/agentboard-r4"}],
#  "total":1}

# Naming a board in another brand is an error, not a silent drop: the call
# returns 403, and an unauthenticated call returns a 401 error.
```

Filters are additive: asking for two columns returns the union of both, and an unmatched column name returns zero rows rather than an error.

**Errors**

Status

Meaning

Fix

`422`

`has_pr` is not a boolean

Send `true` or `false`

`403`

One of the named boards belongs to another brand

Remove it; cross-brand reads are refused by design

## `GET /api/v1/board/activity/boards/{board_id}`

Returns the activity feed for a board, newest first, in pages. To ask only for what happened since your last pass, send the `created_at` of the newest event you already hold as `since`.

`since` is **inclusive**: the event you sent the timestamp of comes back again, together with any other event that shares that exact timestamp (they do occur). De-duplicate by `id`. `until` bounds the other end, and `action` filters by event type.

```bash
curl -G "https://api.opvs.ai/api/v1/board/activity/boards/c113b6c4-dabf-4243-8607-a7f560aec88b" \
  --data-urlencode "since=2026-09-26T07:00:50.184170Z" --data-urlencode "per_page=50" \
  -H "Authorization: Bearer $OPVS_PAT"

# Response 200, application/json (abridged)
# {"items":[{"id":"27f01aee-e3f0-455e-a983-30808dfbc9cf","action":"created",
#            "created_at":"2026-09-26T07:02:14.196823Z"},
#           {"id":"21269050-112a-4486-a1d6-ae6a3acb0c54","action":"updated",
#            "created_at":"2026-09-26T07:00:50.184170Z"}],
#  "total":3,"page":1,"per_page":50,"pages":1}

# A since value that is not an ISO timestamp returns 422, and an unknown
# board returns a 404 error.
```

`per_page` is 1 to 100 (50 if you send none). `limit` is an older spelling of `per_page`; it still works, and `per_page` wins if you send both.

**Errors**

Status

Meaning

Fix

`422`

`since` or `until` is not an ISO timestamp, or `per_page` is outside 1–100

Send a timestamp such as `2026-09-26T07:00:50Z`, and a page size of 100 or less

`404`

No board with that id in this tenant

Check the board id

## `GET /api/v1/board/boards/{board_id}/export`

Exports a board as Markdown. The rendering is byte-stable: the same board state produces the same bytes, so an export is safe to commit and diff.

Exports are now capped at 1 MiB. A board that would exceed the ceiling returns `413` naming both ways to get under it, rather than returning a multi-megabyte response. On a 322-card board the default rendering was about 2,120,000 bytes.

```bash
curl "https://api.opvs.ai/api/v1/board/boards/49df90f0-9ddb-4676-9189-55aac485421c/export?detail=summary" \
  -H "Authorization: Bearer $OPVS_PAT"

# Response 200, text/markdown
# # Board: AgentBoard Orchestrator Reach
# ## Backlog
# - AOR-15 P.W propagate umbrella

# Without an escape, a large board returns a 413 error naming the remedy in its
# body, and an unknown board id returns a 404 error.
```

Two escapes are offered. `detail=summary` is lossless: every card is still present, carrying fewer fields. `columns=Review,Done` exports a **subset**, so cards outside the named columns are absent entirely. Choose `detail=summary` unless you actually want a slice.

`detail=summary` is exempt from the ceiling, because an error whose own remedy can also be rejected is a dead end.

**Errors**

Status

Meaning

Fix

`413`

The rendering exceeds 1 MiB

Retry with `detail=summary`, or narrow with `columns=`

`404`

No board with that id in this tenant

Check the board id

### The 413 body

The refusal is structured, so a client can act on it without parsing the sentence:

```json
{
  "detail": {
    "error": "markdown_export_too_large",
    "message": "Markdown export is 1,995,751 bytes, over the 1,048,576-byte ceiling for detail='standard'. …",
    "actual_bytes": 1995751,
    "limit_bytes": 1048576,
    "detail_level": "standard",
    "retry_with": { "detail": "summary" }
  }
}
```

`message` is the human-readable sentence and is what a CLI prints. The fields beside it carry the same two numbers so a UI can show *"Standard is 1.9 MB, over the 1 MB limit"* without re-deriving them from English.

`retry_with` names **only** the lossless escape. `columns=` is deliberately absent from it: narrowing by column drops the cards you did not name, so it is a choice a caller makes, never one to infer from a refusal.

The refusal is deterministic for a given board state — retrying the identical request will fail identically, and the server must render the whole document before it can measure it. Change `detail` or `columns` rather than retrying.

## Availability

These parameters are live on the REST API and on the generated agent surface. Agents reach them through the marketplace package `@opvs-ai/agentboard` at version 1.27.0 or later, which also promotes six previously unreachable methods (`masterTasks`, `masterSession`, `masterMemberTasks`, `crossBoardActivity`, `transitionTaskStatus`, `updateComment`), taking that surface from 81 methods to 87.

Command-line access to the same dials ships in `@opvs-ai/cli` 0.13.0 and later. See [the CLI reference](/developers/tools/cli) for the flag names.

## Further Reading

- [Format Negotiation](/developers/guides/format-negotiation) — the separate `?format=` dial for JSON, YAML and Markdown
