curl and jq for the REST examples. No workspace yet? Set one up from your own assistant first.
1. Mint an agent key
Open Settings → Agent keys at app.attensira.com/settings/access/agents and mint a key for the program that will call us. Agent keys sits under Your assistant in the settings menu, which is why error messages may point you to Settings › Your assistant:- Agent: a short lower-case name, such as
ci-bot. Its work is credited to this name (“via ci-bot”). - Steer: switch it on. It adds the scope
steer, which declining needs (step 4). - Approve, per lane: for the Your site lane, choose Up to what can be undone. That adds the scope
approve:own_site:reversible, which approving needs (step 4). - Leave everything else switched off. The key can always read.
Scopes
Shell
atn_live_ followed by 32 hexadecimal characters. Keys are minted only in the app, by a person: no key or connection can mint one. See API keys for expiry and revocation.
2. First read
REST
Shell
200 returns your organisation, the workspace the key names as its default in project, every workspace in the organisation in workspaces, and the key’s scopes. project is null when the organisation has several workspaces and the key names no default. Every /v1 endpoint takes an optional ?project=<workspace id>; leave it out and the key’s default is used. See Authentication.
MCP
Add the server to Claude Code with the same key:Shell
brief, the tool to start from. It takes no arguments and returns the workspace it read (workspace_id), loop health (loop), and what needs a person (needs_you: a count and the top three, each by uri). Read any of those in full with get and its uri. For Cursor, VS Code, ChatGPT, Codex and the other clients, and for signing in through the browser instead of using a key, see Connect a client.
To see the protocol itself, here is the same read over plain HTTP. The server speaks Streamable HTTP: initialize returns an Mcp-Session-Id header, each later call sends it back, and the client confirms with notifications/initialized before its first call.
Shell
structuredContent is the brief. The response may arrive as JSON or as a server-sent event (data: {...}), as the Accept header allows. A failed call comes back as a result with isError: true and a message written for the model to act on, never as a JSON-RPC error.
3. Find something to approve
The Inbox is everything waiting on a person or an agent with the right scope. List it, and keep the rows that are work items:Shell
brief already named the top three in needs_you: call get with one’s uri, and read its id and change.proposal.captured_at from the result for step 4. Over REST, read one in full by its ref, and look at what it would change, in which lane, and how far it reaches:
Shell
effect_class is how far the change reaches: pure, reversible, compensable or irreversible. The key from step 1 approves pure and reversible work on your own site, and nothing else. Pick a row whose lane is own_site and whose effect_class is pure or reversible. A row with no change has nothing to approve this way.
An empty list means nothing is waiting. That is a correct answer, not an error.
4. First approval
Approve the work item by itsid, pinned to the version you read with expected_captured_at:
Shell
approve_work_item with the same id and expected_captured_at.
A 200 returns the item’s id and its new state, and next_item_ref when something else is waiting. Approving twice approves once, so a retry after a timeout is safe. If the proposal was revised after you read it, the call is refused with 409 so you never approve a version you did not see; read it again and decide.
The approval is the part only a scope allows, so it is also where a refusal is most likely:
To say no instead, decline with a reason (
wrong, not_now, already_done or never_touch): POST /v1/inbox/{id}/decline, or decline_work_item over MCP. Declining needs steer, not an approve scope. See Who can approve what.
Scopes in one table
Every call needs one scope, checked before the call does anything, so a refused call has no side effects.
No scope raises what the agent may do on its own, raises a spend ceiling, grants consent, connects a credential, mints a key or approves an irreversible change. A person does those in the app. The full grammar is in Scopes.
Errors
Every non-2xx response fromapi.attensira.com is one envelope:
403 response
code when it is there, and on type when it is not: an error caught at the door, such as a malformed key, carries only type, message and status. Never switch on message; it is written for a person or an agent to read, and its wording changes. A 401 means check the key; a 403 means the key works but this call is refused, so do not throw the key away. Over MCP the same refusal is a tool error (isError: true) whose text carries the same fix and docs_url. Every type and code is on Errors.
Events
Instead of polling the Inbox, have it pushed to you. Create a webhook endpoint with a key that holdsconfigure:
Shell
work_item.created fires when the agent files work waiting on a person, such as the work items from step 3. needs_you fires when a run parks on an approval or questions, or files an Inbox row for a blocked step, a decision or a question; a proposed work item is sent as work_item.created, never again as needs_you. change.live fires when a change goes live. Each delivery is a signed POST with id, type, created_at, project_id and data, and an Attensira-Signature header to verify. GET /v1/webhooks/events lists every event type. Webhooks are on webapp.attensira.com, not api.attensira.com, and take an API key, not an OAuth token. Their errors use the backend’s own shape, with no outer error object: see Errors from the webapp host. See Webhooks for the events, signatures and retries.
Where next
- MCP tools and REST endpoints: the full surface, each tool beside its endpoint.
- Recipes: longer flows from an assistant.
- Sessions and Streaming: asking the agent to do work, which needs a
spendscope.