Approval UI
A card the reviewer can decide from
in under five seconds.
Every approval lands in a full-width card with the rendered HTML body your agent sent, the actions it's asking for, and nothing else. Skim, decide, move on.
Channel · Content publishing
3 pending
apr_4f9c1a · 2m ago · content-bot
Publish blog post: "A quieter way to ship agents"
I drafted the launch post you asked for. Ready to publish to the blog and schedule the social posts.
Word count
1,284
Reading time
6 min
Category
Engineering
Show first 200 words
There's a moment in every AI-agent project where the thing goes from toy to tool — and it's almost always the moment the team realises it can't ship without a human on the rail…
Responds to webhook · api.contentapp.com/webhook
Rich HTML bodies
Your agent writes the story. The reviewer reads it.
Submit any sanitised HTML as the body. Tables, images, code blocks, collapsible sections. The reviewer sees exactly what the agent is asking to do, in the shape the agent thought would be clearest.
- Sanitised by DOMPurify on render — scripts, iframes, and inline handlers stripped
- Images must be https; links open in a new tab by default
- Works with prose, tables, code, diffs, details/summary, and small media
Migrate 2,341 rows from staging → prod
The migration adds a team_id column and backfills it from the legacy ownership table.
| Table | Rows |
|---|---|
| projects | 1,204 |
| proj_members | 987 |
| activities | 150 |
ALTER TABLE projects ADD COLUMN team_id uuid; UPDATE projects p SET team_id = ( SELECT team_id FROM legacy_owner o WHERE o.project_id = p.id );
Custom actions
Two buttons aren't always enough.
Approve and Deny ship by default. Add up to three extra actions per channel — 'Publish as draft', 'Send to editor', 'Reject with reason' — and your webhook receives the exact action the reviewer chose.
- Configure per channel, not per approval
- Labels, colours, and order are yours
- The chosen action ships to your webhook as chosen_action
Denial reasons
A reviewer can't say 'no' without a why.
Deny prompts for a reason before it commits. The text is stored with the approval, flows to your webhook, and shows up in the history view for anyone auditing the decision later.
- Required by default, configurable per channel
- Appears on the webhook payload as denial_reason
- Rendered into the history view so the audit trail is self-explaining
denial_reasonGive your humans a review loop they'll actually use.
Spin up a channel, submit your first approval with curl, and watch the card render.