Atribu
For AI Agents

For AI agents

Everything a program needs to take a human from a fresh email to a Purchase landing in Meta — the bootstrap, the discovery documents, the readiness read, and the hand-off pattern.

You are an AI agent, or you are building one. You have no Atribu account, no workspace, no API key, and no browser of your own. This page is the whole surface, in the order you need it.

The golden path is the same thirteen steps a human walks. Twelve of them are yours. Exactly one — creating the account — is a person's, and even that happens inside a URL you hand over.

1. Read these first

documentwhat it is
llms.txtthe map: every product surface, every doc page, every machine-readable entry point
llms-full.txtevery documentation page concatenated as one markdown document
openapi.jsonOpenAPI 3.1 for all of /api/v1
/api/docs/{path}any single doc page as raw markdown

Discovery documents for the OAuth surfaces, both live and standards-compliant:

https://www.atribu.app/.well-known/oauth-authorization-server   # RFC 8414
https://mcp.atribu.app/mcp                                      # Streamable-HTTP MCP

2. The bootstrap: there is no signup API

Account creation is a human act behind a captcha. The bootstrap is therefore an OAuth hand-off, exactly like connecting Gmail or Cloudflare:

Register a client and build the authorize URL

Dynamic Client Registration (RFC 7591) at POST /oauth/mcp/register. Public clients only — PKCE (S256; plain is refused) proves the exchange.

Open this to connect your Atribu account: https://www.atribu.app/oauth/mcp/authorize?response_type=code&client_id=…

An unrecognised email goes straight to signup with the pending authorization preserved, and the email-confirmation link returns them to your consent screen — not to a dashboard. A user with no workspace is told so plainly: "You have no workspace yet — your agent will create one after you approve."

Exchange the code, then keep the token

POST /oauth/mcp/token. The access token lives 1 hour; the refresh token lives 60 days and rotates on every use — store the new one each time. Replaying an old refresh token burns the whole family, which is the reuse detection working.

Step by step, with every request and response: API quickstart · MCP quickstart.

One token, two surfaces

Since #1084 an atb_user_… token is a first-class /api/v1 principal. The token the consent screen produced works on https://mcp.atribu.app/mcp and on https://api.atribu.app/api/v1/*. Nothing else has to be minted, and an API key is only for a machine job that belongs to a profile rather than to a person.

3. Call whoami before anything else

curl -sX POST https://mcp.atribu.app/mcp \
  -H "Authorization: Bearer atb_user_…" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/call",
       "params":{"name":"whoami","arguments":{}}}'

It costs zero units and answers with the workspaces, profiles, currency, PII mode and write-back state the caller actually has. The REST equivalent is GET /api/v1/workspaces, which needs no profile_id — which is exactly why it is the right first call.

A user token must name a profile

An API key is one profile. A user token may reach several, so ?profile_id=<uuid> is required on profile-scoped routes: its absence is a 400, never a guess.

4. Bootstrap the objects

workspace → profile → tracking key → readiness
curl -sX POST https://api.atribu.app/api/v1/workspaces \
  -H "Authorization: Bearer atb_user_…" -H "Content-Type: application/json" \
  -d '{"name": "My Agency"}'

curl -sX POST https://api.atribu.app/api/v1/profiles \
  -H "Authorization: Bearer atb_user_…" -H "Content-Type: application/json" \
  -d '{"workspace_id": "WORKSPACE_ID", "name": "My Website"}'

curl -sX POST "https://api.atribu.app/api/v1/tracking/keys?profile_id=PROFILE_ID" \
  -H "Authorization: Bearer atb_user_…"

curl "https://api.atribu.app/api/v1/profile/readiness?profile_id=PROFILE_ID" \
  -H "Authorization: Bearer atb_user_…"

Both creates are idempotent — the workspace on (you, name) for a short window, the tracking key on "this profile already has an active one". Retrying after a dropped connection is safe and is not a duplicate.

5. Readiness is your program counter

GET /api/v1/profile/readiness

Twelve steps in golden-path order, each with a status, a why, a next and a docs_url. summary.next_step is the first non-done step — first, not "most important": the order is the priority.

Every non-done step's next is one of two shapes, and the difference is the whole design:

shapemeaning
{"method": "…", "path": "…"}a call you can make right now with the credential you hold
{"handoff_kind": "…"}a class of action only a person can take

Four hand-off kinds, and each is honest about why: connect (an OAuth consent needs a browser), sign_dpa (a signature forged on someone's behalf is not a signature), checkout (a payment), contact_support (Atribu itself has to change something).

Do not use onboarding_completed for this. It means one thing — a person pressed Finish in the wizard — and every wizard step is skippable. Full detail: Readiness.

6. The hand-off pattern

Give the user this URL. Poll this handle. Continue when it settles.

GET /api/v1/handoffs/{id}
  1. Mint through the route for the thing you need — a hand-off with nothing behind it is a URL nobody can complete, so there is deliberately no POST /api/v1/handoffs.
  2. Hand the URL over. It is returned only while status is pending. Capture it once and give it to your user; re-poll for status, not for the URL. Whoever holds it can complete it — no Atribu account, no session, no desktop.
  3. Poll. An expired hand-off is a 200 with status: "expired", never a 404. A 404 means the id names nothing your workspace minted.

45 minutes by default. Always read expires_at; a kind whose underlying object has its own clock mirrors that clock instead. Full detail: Hand-offs.

Stop polling where you can

connection.connected, connection.reconnect_required, handoff.completed, recompute.completed, conversion.attributed, export.completed and export.failed all fan out as webhooks (#1049). Subscribe with providers: ["atribu"] — every platform lifecycle event carries that provider, not a channel, and a subscription that omits it receives none of them. See Webhooks.

7. What is live over MCP

The whole golden path is. tools/list carries real input schemas, readOnlyHint / destructiveHint annotations, resources and prompts (#1062). Start here:

tooluse
whoamiwho am I, what can I reach, what is missing
get_readinessthe twelve-step checklist
list_workspaces, list_profilesnavigation
apply_recommendation, send_meta_conversionsthe two Meta write-backs, preview → dry_run → confirm

whoami also folds in a readiness rollup — next_step plus the missing_steps — so one free call answers "who am I and what is left". When that forwarded read fails it answers null with a readiness_error naming the cause, never an empty missing_steps that would read as "all done".

The onboarding family (#1058)

Sixteen tools that walk the golden path end to end, grouped by the step they serve:

steptools
3 — workspace + profilecreate_workspace · create_profile
5 — trackerissue_tracking_key · get_tracker_installer
6–8 — connectstart_connect · get_handoff
9 — conversionslist_outcome_events · suggest_conversion_definitions · create_conversion_definition · set_attribution_windows
11 — send to Metaconfigure_meta_capi · send_test_event
12 — DPA + plansign_dpa · start_plan_upgrade
practicecreate_demo_profile · delete_demo_profile

Every write tool takes `mode`, and `preview` is free

mode: "preview" describes what the call would do and writes nothing; mode: "confirm" performs it. Confirms are idempotent — the key is derived from the arguments and recorded in the workspace's write-back audit — so a retried confirm replays rather than doubling. This is the same ceremony the two older write-backs use, not a second one.

Every one is a thin wrapper on exactly one /api/v1 route. They forward your bearer and return what the API says; the client, the error map, the write gates and the audit write live in one shared spine rather than sixteen copies. So no tool holds business logic unreachable over REST — which is what makes "drive it over MCP" and "drive it over REST" the same journey rather than two implementations that can drift.

The approval hand-off is live (#1050)

A confirm from a principal that is not a workspace owner/admin — or any caller passing require_approval: true — mints an approve hand-off carrying the exact preview and writes nothing; your human approves in a signed-out browser and you poll the same result payload back. It is what makes a non-admin token useful instead of a dead end: before it, such a token was refused outright with no way to ask anyone.

8. Practising against data that is not real (#1069)

One call, and the workspace has something to read
curl -sX POST "https://api.atribu.app/api/v1/profiles/demo?workspace_id=WORKSPACE_ID" \
  -H "Authorization: Bearer atb_user_…"

Sixty days of a fictional clinic's Meta traffic, and a Meta CAPI destination that records what it would have sent instead of calling Meta. Over MCP: create_demo_profile / delete_demo_profile. Full detail: Demo profile.

The engine runs for real, which is why the counts are a forecast

The seed writes inputs — ad structure, daily spend, identities, touches, and outcome_events through the same CRM-ingest primitive every real provider uses — then enqueues an ordinary recompute. conversions and attribution_touchpoints across all five models come out of the pipeline, not out of the seed, and the recompute is asynchronous. So the counts in the response say what the recompute will produce; poll GET /api/v1/overview for what it did.

Its own reads show its own money — that is the point — and every read that spans a workspace's profiles leaves it out, so a demo profile can never inflate a pacing aggregate or a leaderboard. DELETE is a hard delete, not an archive (an archived demo would keep answering with its numbers), and deleting nothing answers 200 with deleted: false rather than 404: a workspace with no demo profile is the state you asked for, and a retry must not look like a failure.

9. The skill

If your host is Claude Code, skills/atribu-attribution-skill packages the tool vocabulary, the defaults (cash ROAS, PII masked) and the write-back safety rules. See the skill.

Where to go next

On this page