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

# Attention

> Everything waiting on you, grouped by relationship. The Monday door, read-only.

<Warning>
  **Draft — ahead of the backend; nothing here is live yet.** This surface is
  written against the design, not against a running server. There is no
  `api.contracts.io` to call, no key to issue, and every response below is an
  illustration rather than a recording. It is published so the shape can be argued
  with before it is built.

  It also predates **CONTRACT-SCHEMA v0** and still uses the older nouns — *ask* for
  Proposal, *receipt* for Version, *relationship* for Room. The
  [glossary](/glossary) maps them.
</Warning>

One endpoint answers the only question that matters on a Monday morning: **what is waiting on me?**

## List what needs attention

<ResponseField name="GET /attention" type="endpoint">
  Scopes: `contracts:read`. `Contracts-Acting-Person` is **required**.
</ResponseField>

Returns everything waiting on the acting person, grouped by relationship. Cursor-paginated with
`starting_after` and `limit`, ordered by urgency.

```
GET /attention?limit=25&starting_after=atn_18
Contracts-Acting-Person: per_9
```

```json 200 OK theme={null}
{
  "data": [
    {
      "relationship": "rel_4",
      "counterparty": "Northwind",
      "rows": [
        {
          "id": "atn_19",
          "kind": "resign",
          "contract": "con_1",
          "title": "Northwind MSA",
          "state": "agreed",
          "since": "2026-08-22T10:00:00Z",
          "why_you": "You signed this, then the words moved. Sign again or it can't complete.",
          "letter": null,
          "signature": "sig_1",
          "words_hash": "sha256:be71…"
        },
        {
          "id": "atn_20",
          "kind": "letter",
          "contract": "con_2",
          "title": "Northwind order form",
          "state": "negotiating",
          "since": "2026-08-24T09:12:00Z",
          "why_you": "Northwind wants nine months instead of twelve.",
          "letter": "let_2",
          "asks": 1,
          "unsent": false
        }
      ]
    }
  ],
  "has_more": false,
  "next_cursor": null,
  "waiting_on_them": [
    {
      "relationship": "rel_7",
      "counterparty": "Helix",
      "contract": "con_8",
      "since": "2026-08-19T16:20:00Z"
    }
  ]
}
```

## Four kinds of row

Not every row is a letter. Somebody has to sign, somebody signed and then the words moved, somebody on
your own side has to say yes before a position goes out. Every row carries a `kind`, so a client can
branch on it.

| `kind`      | Required beyond the common fields  | What it means                                                             |
| ----------- | ---------------------------------- | ------------------------------------------------------------------------- |
| `letter`    | `letter`, `asks`, `unsent`         | A round landed on you.                                                    |
| `sign_wait` | `words_hash`, `required_signers[]` | Everyone agreed. You are one of the people who has to sign.               |
| `resign`    | `signature`, `words_hash`          | You signed, then the words moved. Your old signature is void.             |
| `approval`  | `decision`, `requested_by`         | Somebody on your side needs you to approve a position before it goes out. |

Common on every row: `id`, `kind`, `contract`, `title`, `state`, `since`, `why_you`.

<ResponseField name="id" type="string" required>
  A stable `atn_` id. It is what `starting_after` takes, and it is what you dedupe on across a poll and
  a webhook-driven refresh.
</ResponseField>

<ResponseField name="kind" type="string" required>
  One of `letter`, `sign_wait`, `resign`, `approval`. This is the field to branch on.
</ResponseField>

<ResponseField name="letter" type="string | null" required>
  Nullable. It is null on every kind except `letter`.
</ResponseField>

<ResponseField name="since" type="string" required>
  When this started waiting on you.
</ResponseField>

<ResponseField name="why_you" type="string" required>
  Why **this** row is yours, in one sentence. Not a role name. A reason, written for a person to read,
  so never the thing your code branches on.
</ResponseField>

<ResponseField name="asks" type="integer">
  On `letter` rows. How many things this round asks of you.
</ResponseField>

<ResponseField name="unsent" type="boolean">
  On `letter` rows. Whether you have something staged on this contract and have not sent it. Private to
  your seat.
</ResponseField>

## Ordering is urgency, not recency

1. **Groups are ordered by their most urgent row.** A group is never split across a page.
2. **Within a group: `resign` first, then `approval`, then `sign_wait`, then `letter`.**
3. **Within one kind, the oldest `since` comes first**, because the thing that has been waiting longest
   is the most urgent.

<Note>
  Newest first would put a letter that arrived this morning above a re-sign row that has been blocking a
  signature for a week. The week-old row is the one holding the deal up, so it sorts to the top.
</Note>

## Pagination

`limit` counts **rows**, never relationships. A group is never split, so a page can come back with
slightly more rows than you asked for in order to finish the last group. `starting_after` takes a row
id, the `atn_` one. `has_more` and `next_cursor` are always present.

## What is waiting on them

`waiting_on_them` is a separate tail of the response, outside `data`. It holds the contracts where the next
move is theirs, each one carrying `relationship`, `counterparty`, `contract` and `since`.

Keeping it out of `data` is the point: the count at the top of your inbox then stays honest about what is
actually yours to do.

## The acting person is required here

<Warning>
  `Contracts-Acting-Person` is **required** on this read, and this is the one read where that is true. An
  org key without it gets `400 acting_person_required`. Not an empty list, and not a merged org-wide
  queue.
</Warning>

This feed belongs to one person, and its `unsent` field is private to that seat, so merging everybody's
queue would push one person's private drafts sideways across the org. See [Errors](/api/errors) for the
envelope.

## It is read-only

<Warning>
  There is no `POST /attention`, no dismiss, no snooze, and no mark-as-read.
</Warning>

A row leaves the list when the thing it was waiting on happens. You send a reply, you sign, the contract
executes or is cancelled. Attention is a **computed view of state**, not a mutable inbox, so it can never
disagree with the contracts it describes.

Rows are cleared by acting on the contract, so the verb you want lives on the contract:

| Row         | What clears it                                                 |
| ----------- | -------------------------------------------------------------- |
| `letter`    | `POST /contracts/{id}/replies`                                 |
| `sign_wait` | `POST /contracts/{id}/signatures`                              |
| `resign`    | `POST /contracts/{id}/signatures` again, against the new words |
| `approval`  | The approver acts in the app. There is no HTTP verb.           |

A forwarded mail thread also shows up in this feed when `ingress.detected` fires, and that item has two
verbs of its own: `POST /ingress/{id}/accept` and `POST /ingress/{id}/reject`.

## Approvals appear here, but you approve in the app

An `approval` row tells an approver what is waiting on them, and that is all the API does. Approval
routing is explicitly not a v1 API surface, so `GET /attention` shows an approver their rows and there is
no endpoint to approve through.

## Building an inbox

Poll `GET /attention` on login, then refresh it from [webhooks](/api/webhooks) rather than on a
timer. Seven events move this feed:

`letter.received` · `reply.sent` · `contract.agreed` · `words.changed` · `contract.executed` ·
`contract.cancelled` · `ingress.detected`

<Warning>
  The last five mint and clear the kinds that are not letters. Refresh only on letters and a re-sign row
  turns up late, while a sign-wait row never clears at all.
</Warning>

<Note>
  `unsent` is the field that makes a letter row honest. A contract with a reply staged and nothing sent is
  waiting on **you**, not on them, and a Monday inbox that hides that is lying to its reader.
</Note>
