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

# Agent auth (auth.md) reference

> Developer protocol reference for obtaining a Migma API key on a user's behalf: discovery, claim, token, and scopes

<Note>
  Agent registration is in beta. For the user-facing overview of how approval works, see [Agent registration](/agent-registration). This page is the deeper protocol reference for developers building agents.
</Note>

Migma follows the [auth.md](https://workos.com/auth-md/docs) convention, so an agent can discover the flow and obtain its own scoped API key without a human copying a secret. The flow is **user-claimed**: the agent registers intent, the user approves with a short code, and the agent polls for the key.

Base URL: `https://api.migma.ai`

The skill file an agent reads is served at `https://api.migma.ai/auth.md`.

## Discovery

Agents discover the endpoints and supported scopes from the well-known metadata. The `agent_auth` block in the authorization-server document lists the claim and identity endpoints.

```
GET https://api.migma.ai/.well-known/oauth-authorization-server
GET https://api.migma.ai/.well-known/oauth-protected-resource
GET https://api.migma.ai/.well-known/oauth-protected-resource/mcp
```

The authorization-server document includes `scopes_supported`, `token_endpoint`, `grant_types_supported`, and the `agent_auth` endpoint map.

An agent that hits a protected API without a valid key receives `401` with a `WWW-Authenticate` challenge that points back to the protected-resource metadata, so it can find this flow:

```http theme={null}
WWW-Authenticate: Bearer resource_metadata="https://api.migma.ai/.well-known/oauth-protected-resource"
```

The MCP endpoint returns the same challenge pointing at its own resource metadata (`/.well-known/oauth-protected-resource/mcp`).

## Endpoints

| Method | Path                                        | Auth | Rate limit         | Purpose                                                   |
| ------ | ------------------------------------------- | ---- | ------------------ | --------------------------------------------------------- |
| `GET`  | `/auth.md`                                  | None | —                  | The agent skill file                                      |
| `GET`  | `/.well-known/oauth-authorization-server`   | None | —                  | Authorization-server metadata (scopes, endpoints)         |
| `GET`  | `/.well-known/oauth-protected-resource`     | None | —                  | Protected-resource metadata for the API                   |
| `GET`  | `/.well-known/oauth-protected-resource/mcp` | None | —                  | Protected-resource metadata for the MCP server            |
| `POST` | `/agent/identity`                           | None | 10 / hour per IP   | Step 1 — register intent, get a claim token and user code |
| `POST` | `/agent/identity/claim`                     | None | 20 / hour per IP   | Step 4 — refresh an expired user code                     |
| `POST` | `/oauth2/token`                             | None | 120 / 5 min per IP | Step 3 — poll for the issued key                          |

The claim page endpoints (`/agent/identity/claim/lookup` and `/agent/identity/claim/complete`) are used by the Migma web UI when the user enters their code. Agents do not call them.

## Step 1 — Register intent

```bash theme={null}
curl -X POST https://api.migma.ai/agent/identity \
  -H "Content-Type: application/json" \
  -d '{
    "type": "service_auth",
    "login_hint": "user@example.com",
    "agent_name": "Claude",
    "scope": "audience:read campaign:read email:send"
  }'
```

* `type` — required, must be `service_auth`. Identity-assertion and anonymous registration are not supported yet.
* `login_hint` — required. The email of the user the agent acts for. It is display only; it grants nothing by itself.
* `agent_name` — optional. Shown to the user at approval. Use your product name.
* `scope` — optional, space-delimited. Omit for a read-mostly default. Request only what you need.

Response:

```json theme={null}
{
  "claim_token": "<JWT, keep private>",
  "claim": {
    "user_code": "XXXX-XXXX",
    "verification_uri": "https://migma.ai/claim",
    "verification_uri_complete": "https://migma.ai/claim?code=XXXX-XXXX",
    "expires_in": 600,
    "interval": 5
  }
}
```

## Step 2 — Send the user to approve

Surface `verification_uri_complete` (or `verification_uri` plus `user_code`) to the user. They sign in to Migma, review the agent name and requested scopes, and confirm the code.

## Step 3 — Poll for the key

Poll the token endpoint every `interval` seconds (default 5):

```bash theme={null}
curl -X POST https://api.migma.ai/oauth2/token \
  -H "Content-Type: application/json" \
  -d '{
    "grant_type": "urn:workos:agent-auth:grant-type:claim",
    "claim_token": "<from step 1>"
  }'
```

Responses:

| Status | `error`                 | What to do                                                                      |
| ------ | ----------------------- | ------------------------------------------------------------------------------- |
| `400`  | `authorization_pending` | The user has not confirmed yet. Keep polling.                                   |
| `400`  | `expired_token`         | The user code expired. Refresh it (Step 4), surface the new code, keep polling. |
| `400`  | `invalid_grant`         | The claim is no longer usable. Start over at Step 1.                            |
| `200`  | —                       | Success. The key is in `access_token`.                                          |

Success body:

```json theme={null}
{
  "access_token": "sk_live_…",
  "token_type": "bearer",
  "scope": "audience:read campaign:read email:send",
  "key_name": "Agent: Claude"
}
```

<Warning>
  The `access_token` is a Migma platform API key and is returned **exactly once** — store it securely now. Further polls return `invalid_grant`.
</Warning>

## Step 4 — Refresh an expired user code

Only when polling returned `expired_token`:

```bash theme={null}
curl -X POST https://api.migma.ai/agent/identity/claim \
  -H "Content-Type: application/json" \
  -d '{ "claim_token": "<from step 1>" }'
```

Returns a fresh `claim` object with a new `user_code`. The whole registration expires one hour after Step 1; after that, start over.

## Code lifecycle

* The `user_code` expires after `expires_in` seconds (600 by default).
* Refresh an expired code with `POST /agent/identity/claim` using the same `claim_token`.
* The whole registration expires one hour after Step 1.
* The `access_token` is issued exactly once; re-polling after issuance returns `invalid_grant`.

## Poll loop example

```javascript theme={null}
async function registerAgent(loginHint) {
  const intent = await fetch('https://api.migma.ai/agent/identity', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      type: 'service_auth',
      login_hint: loginHint,
      agent_name: 'Claude',
      scope: 'audience:read campaign:read email:send'
    })
  }).then((r) => r.json());

  let { claim_token, claim } = intent;
  console.log('Approve at:', claim.verification_uri_complete);

  while (true) {
    await new Promise((r) => setTimeout(r, claim.interval * 1000));

    const res = await fetch('https://api.migma.ai/oauth2/token', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        grant_type: 'urn:workos:agent-auth:grant-type:claim',
        claim_token
      })
    });
    const body = await res.json();

    if (res.ok) return body.access_token; // sk_live_…

    if (body.error === 'authorization_pending') continue;

    if (body.error === 'expired_token') {
      const refreshed = await fetch('https://api.migma.ai/agent/identity/claim', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ claim_token })
      }).then((r) => r.json());
      claim = refreshed.claim;
      claim_token = refreshed.claim_token;
      console.log('New code:', claim.verification_uri_complete);
      continue;
    }

    // invalid_grant or anything else — start over
    throw new Error(`Registration failed: ${body.error}`);
  }
}
```

## Scopes

Scopes are space-delimited in the `scope` field of Step 1. Omit the field for a read-mostly default; request only the scopes the agent needs. The available scopes are published in `scopes_supported` in the discovery document:

| Scope            | Grants                                                   |
| ---------------- | -------------------------------------------------------- |
| `audience:read`  | View subscribers, tags, and segments                     |
| `audience:write` | Add and update subscribers, manage tags, create segments |
| `email:read`     | View emails and sending history                          |
| `email:send`     | Send emails and campaigns                                |
| `email:validate` | Run compatibility, link, and deliverability checks       |
| `email:preview`  | Generate device and client previews                      |
| `domain:read`    | View sending domains and verification status             |
| `domain:write`   | Add, verify, update, and remove sending domains          |
| `campaign:read`  | View campaigns and their stats and logs                  |
| `campaign:write` | Create, send, schedule, and manage campaigns             |
| `webhook:read`   | List and view webhooks                                   |
| `webhook:write`  | Create, update, and delete webhooks                      |
| `project:write`  | Import projects and manage project resources             |

## Using the credential

The `access_token` is an ordinary Migma API key. Send it as a bearer token and discover a project to work in:

```bash theme={null}
# 1. List projects to get a projectId
curl https://api.migma.ai/v1/projects?limit=1 \
  -H "Authorization: Bearer sk_live_…"

# 2. Use that projectId on project-scoped requests
curl "https://api.migma.ai/v1/campaigns?projectId=PROJECT_ID" \
  -H "Authorization: Bearer sk_live_…"
```

The key is account-scoped, so pass `projectId` per request; a resource in a project you did not name returns `404`. The key works everywhere a Migma key works: the [REST API](/api-reference/introduction), the [SDK](/sdk), the [CLI](/cli), and [MCP](/mcp-server), typically by setting `MIGMA_API_KEY`.

## Revocation

Agent keys appear in the Migma dashboard under **Settings → API Integration** alongside your own keys. The user can revoke one at any time. A revoked key fails with `401`; the agent should re-register to obtain a new one.
