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

# Developer quickstart

> From an agent key to a first read and a first approval, over MCP and over REST, in about five minutes: the key, the client config, copy-paste curl, scopes, errors and events.

This page takes you from nothing to two working calls: a first read, and a first approval of a change that can be undone. Each step works over the [MCP server](https://docs.attensira.com/mcp/overview.md) and over the [REST API](https://docs.attensira.com/api/overview.md). Both reach the same backend with the same key, and the same scopes decide what each call may do.

You need an Attensira account with a workspace, and `curl` and [`jq`](https://jqlang.org) for the REST examples. No workspace yet? [Set one up from your own assistant](https://docs.attensira.com/start/quickstart.md#set-up-from-your-own-assistant) first.

## 1. Mint an agent key

Open **Settings → Agent keys** at [app.attensira.com/settings/access/agents](https://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.

The key this mints holds:

```text Scopes theme={null}
read steer approve:own_site:reversible
```

Copy the key now: it is shown once. Then put it in your shell:

```bash Shell theme={null}
export ATTENSIRA_API_KEY="atn_live_<your key>"
```

The format is `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](https://docs.attensira.com/account/api-keys.md) for expiry and revocation.

## 2. First read

### REST

```bash Shell theme={null}
curl -s https://api.attensira.com/v1/account \
  -H "Authorization: Bearer $ATTENSIRA_API_KEY" | jq '{org, project, workspaces, scopes}'
```

A `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](https://docs.attensira.com/api/authentication.md).

### MCP

Add the server to Claude Code with the same key:

```bash Shell theme={null}
claude mcp add --transport http attensira https://mcp.attensira.com/mcp \
  --header "Authorization: Bearer $ATTENSIRA_API_KEY"
```

Then ask Claude to call `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](https://docs.attensira.com/mcp/connect.md).

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.

```bash Shell theme={null}
SESSION=$(curl -s -D - -o /dev/null https://mcp.attensira.com/mcp \
  -H "Authorization: Bearer $ATTENSIRA_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"quickstart","version":"1"}}}' \
  | awk 'tolower($1)=="mcp-session-id:" {print $2}' | tr -d '\r')

curl -s -o /dev/null https://mcp.attensira.com/mcp \
  -H "Authorization: Bearer $ATTENSIRA_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -H "Mcp-Session-Id: $SESSION" \
  -d '{"jsonrpc":"2.0","method":"notifications/initialized"}'

curl -s https://mcp.attensira.com/mcp \
  -H "Authorization: Bearer $ATTENSIRA_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -H "Mcp-Session-Id: $SESSION" \
  -d '{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"brief","arguments":{}}}'
```

The result's `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](https://docs.attensira.com/agent/inbox.md) is everything waiting on a person or an agent with the right scope. List it, and keep the rows that are work items:

```bash Shell theme={null}
curl -s https://api.attensira.com/v1/inbox \
  -H "Authorization: Bearer $ATTENSIRA_API_KEY" \
  | jq '.items[] | select(.type == "work_item") | {id, ref, title}'
```

Over MCP, `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:

```bash Shell theme={null}
REF="<a ref from the list>"
curl -s "https://api.attensira.com/v1/inbox/items/$REF" \
  -H "Authorization: Bearer $ATTENSIRA_API_KEY" \
  | jq '{id, title, lane: .work_item.change.lane, effect_class: .work_item.change.effect_class, captured_at: .work_item.change.proposal.captured_at}'
```

`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 its `id`, pinned to the version you read with `expected_captured_at`:

```bash Shell theme={null}
ID="<the id from step 3>"
CAPTURED_AT="<the captured_at from step 3>"
curl -s -X POST "https://api.attensira.com/v1/inbox/$ID/approve" \
  -H "Authorization: Bearer $ATTENSIRA_API_KEY" \
  -H "Content-Type: application/json" \
  -d "{\"expected_captured_at\": \"$CAPTURED_AT\"}"
```

Over MCP it is `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:

| You get | Because | Do this |
| - | - | - |
| `403` `scope_denied`, `details.reason` `scope_ceiling` | The item is above the key's approve scopes, for example `compensable` or another lane. `required_scope` names the exact scope that would cover it. | Leave it for a person, or mint a key with that scope. |
| `403` `self_approval_rejected` | A run this key started proposed it, or it cannot be undone. No key approves irreversible work. | A person approves it in the app. The key can still decline it, because it holds `steer`. |

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](https://docs.attensira.com/agent/approval.md#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.

| Scope | Allows |
| - | - |
| `read` | Every read. Every key holds it. |
| `steer` | Directing the work: asking, steering projects, setting goals, answering questions, declining. |
| `approve:<lane>:<class>` | Approving changes in one lane (`own_site`, `offsite`, `outreach`, `social`, or `*`) up to a class: `pure`, `reversible` or `compensable`. |
| `spend:<usd>/month`, `spend:unbounded` | Starting work that costs credits, up to a monthly ceiling for this key, or with no ceiling of its own. |
| `configure` | Prompts, competitors, topics, markets, skills, automations, webhooks and Brand Book proposals. |

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](https://docs.attensira.com/api/authentication.md#scopes).

## Errors

Every non-2xx response from `api.attensira.com` is one envelope:

```json 403 response theme={null}
{
  "error": {
    "type": "forbidden",
    "code": "scope_denied",
    "message": "this credential does not have the configure scope this call needs",
    "fix": "Ask the key's owner for a key with the configure scope, minted in the app under Settings › Your assistant. How scopes work: https://docs.attensira.com/api/authentication#scopes",
    "docs_url": "https://docs.attensira.com/api/errors#scope_denied",
    "required_scope": "configure",
    "details": {
      "required_scope": "configure",
      "scopes_docs_url": "https://docs.attensira.com/api/authentication#scopes"
    },
    "status": 403
  }
}
```

Branch on `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](https://docs.attensira.com/api/errors.md).

## Events

Instead of polling the Inbox, have it pushed to you. Create a webhook endpoint with a key that holds `configure`:

```bash Shell theme={null}
curl -X POST https://webapp.attensira.com/v1/webhooks \
  -H "Authorization: Bearer $ATTENSIRA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"url":"https://hooks.example.com/attensira","events":["work_item.created","needs_you","change.live"]}'
```

The response is the only time the signing secret is shown in full; store it then. `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](https://docs.attensira.com/api/errors.md#errors-from-the-webapp-host). See [Webhooks](https://docs.attensira.com/api/webhooks.md) for the events, signatures and retries.

## Where next

* [MCP tools](https://docs.attensira.com/mcp/tools.md) and [REST endpoints](https://docs.attensira.com/api/endpoints.md): the full surface, each tool beside its endpoint.
* [Recipes](https://docs.attensira.com/mcp/recipes.md): longer flows from an assistant.
* [Sessions](https://docs.attensira.com/api/sessions.md) and [Streaming](https://docs.attensira.com/api/streaming.md): asking the agent to do work, which needs a `spend` scope.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.