# Build a Review Screen

> Put your review queue on a wall, in Slack or in your admin, driven entirely by each card's actions.

Canonical: https://revnu.com/docs/build-a-review-screen
Last updated: 2026-10-19

The review queue is the agent's inbox for you. Putting it somewhere you already look, a TV in the office, a Slack channel, a tab in your own admin, is the single biggest thing you can do to keep work shipping.

## The rule that makes it easy

Every card carries two fields:

- `actions`: the exact verbs the API accepts for this card right now.
- `primaryLabel`: what the dashboard's main button would say, for example "Send Reply" or "Approve & Publish".

Render one primary button with `primaryLabel` and a secondary control per remaining verb. Never branch on `sourceType` or `kind`; the rules that decide what a card accepts live in one place on the server and change without you.

| Verb in `actions` | Call |
|---|---|
| `approve`, `answer`, `choose` | `POST /review/{id}/answer` with `action` set to the verb |
| `send` | `POST /review/{id}/send` |
| `request_changes` | `POST /review/{id}/request-changes` |
| `snooze` | `POST /review/{id}/snooze` |
| `dismiss` | `POST /review/{id}/dismiss` |

## Which buttons send or publish

Label the buttons with consequences honestly; the person pressing one should know what leaves the building.

| Verb | Effect outside Revnu |
|---|---|
| `send` | Sends the email. |
| `approve` | Depends on the card: publishes a blog post, applies a blog edit, posts a Reddit comment, launches ads, raises the ad budget, or approves buying sending domains. Otherwise it records your yes and Revnu carries on. See [What approve does](https://revnu.com/docs/review-answer.md#what-approve-does). |
| `answer`, `choose` | Answers Revnu's question. Revnu acts on the answer under your approval settings. |
| `request_changes`, `snooze`, `dismiss` | Nothing leaves. |

`primaryLabel` already says it for the main button: "Approve & Send", "Approve & Publish", "Approve & Post", "Launch Campaigns". Use it verbatim.

LinkedIn invite and message cards and gift cards are approved only in the dashboard, so `approve` is absent from their `actions`. Show them with a link to the dashboard's Review page, labelled with `primaryLabel`.

## Polling

`GET /review` returns pending and needs-revision cards, newest first within priority band. Every 10 to 30 seconds is plenty and far inside the read limit. Diff by `id` and `updatedAt` to animate changes.

## Handling a 409

Two people, or a person and the trust ladder, can act on the same card. A verb the card no longer accepts is a `409` whose body carries the current `actions`. Re-render from that and move on; nothing was double-applied.

## A Slack bot in twenty lines

```ts
// Post new cards to a channel; people approve in the dashboard or with your own buttons.
const res = await fetch("https://revnu.com/api/v1/review?status=pending&limit=50", {
  headers: { Authorization: `Bearer ${process.env.REVNU_API_TOKEN}` },
});
const { items } = await res.json();
for (const card of items.filter((c) => !seen.has(c.id))) {
  seen.add(card.id);
  await postToSlack(`${card.title}: ${card.primaryLabel} (${card.actions.join(", ")})`);
}
```

`seen` and `postToSlack` are yours. A token with `review:read` alone is enough to post; add `review:write` only to the process that handles button presses.

## Frequently asked questions

### Can I show the full draft on the screen?

Yes. `GET /review/{id}` returns the card's payload with long text capped at 8 KB per field, which is enough for any draft the agent writes.

### What happens to a card nobody touches?

If the trust ladder has earned it, the card ships itself at `autoResolveAt`. Snoozing cancels that deadline.
