# Probe Research — auth.md

You are an agent. This page tells you how to sign your user up or in to **Probe Research** without a browser form, using the [auth.md](https://github.com/workos/auth.md) agent-registration protocol (v0.6.0). Follow the steps in order: discover → register → claim → exchange → use → refresh → revoke.

Probe records a team's ML research: projects, experiments, runs, metrics, files and notes. New to Probe? Read [agents.md](https://www.useprobe.ai/agents.md) first: it covers the whole journey, including onboarding a brand-new user and installing Probe on their machine.

Hosts: the API (also the authorization server) is `https://api.research.prbe.ai`; the dashboard is `https://research.prbe.ai`. **Take every endpoint URL from the metadata in Step 1**, never from this page.

## Step 1 — Discover

```http
GET https://api.research.prbe.ai/.well-known/oauth-protected-resource
```

Take `authorization_servers[0]`, then:

```http
GET <authorization_server>/.well-known/oauth-authorization-server
```

Read `token_endpoint`, `revocation_endpoint` and the `agent_auth` block (`identity_endpoint`, `claim_endpoint`). Probe accepts `identity_types_supported: ["anonymous", "service_auth"]`. `identity_assertion` (ID-JAG) is **not** supported: `assertion_types_supported` is empty.

## Step 2 — Pick a method

1. **You know the user's email** → `service_auth` (recommended).
2. **You don't** → `anonymous`, and ask for the email before Step 4.

Ask for the email they use (or will use) to sign in to Probe with Google. If they have no Probe account yet, that Google sign-in creates one.

Before registering, tell the user what you are about to do: connect to Probe Research and, once they approve, read and write their research.

## Step 3 — Register

**service_auth**

```http
POST <identity_endpoint>
Content-Type: application/json

{"type": "service_auth", "login_hint": "user@example.com"}
```

```json
{
  "registration_id": "reg_…",
  "registration_type": "service_auth",
  "claim_url": "<claim_endpoint>",
  "claim_token": "clm_…",
  "claim_token_expires": "…",
  "post_claim_scopes": ["research:read", "research:write", "research:delete"],
  "claim": {"user_code": "123456", "expires_in": 600, "verification_uri": "https://research.prbe.ai/authorize?agent=…", "interval": 5}
}
```

Go to Step 4b.

**anonymous**

```http
POST <identity_endpoint>
Content-Type: application/json

{"type": "anonymous"}
```

The response carries `identity_assertion`, `pre_claim_scopes: []`, `claim_token` and `post_claim_scopes`. Probe grants **nothing** before a person claims you: that pre-claim assertion exchanges for no token. Go to Step 4a.

`claim_token` is a secret. Your conversation can span several turns and background processes may not survive between them, so save every credential you get here (`claim_token`, `identity_assertion`, `access_token`) to **one private file only you read**, outside any repository, for example `~/.config/probe/agent-auth.json` with mode `600`. Never print a credential, paste it into chat, or commit it. Delete `claim_token` from that file once Step 4 succeeds.

## Step 4 — Claim (the user's one browser step)

**4a. anonymous only** — start the ceremony with the user's email:

```http
POST <claim_endpoint>
Content-Type: application/json

{"claim_token": "clm_…", "email": "user@example.com"}
```

The `claim_attempt` block in the response has the same shape as `claim` above.

**4b. Hand off to the user**, in one message:

> Open this link, sign in with Google, and type this 6-digit code into the page: **123456**
> https://research.prbe.ai/authorize?agent=…

The code goes into the page, not back to you. Never open the link yourself and never complete the page for them. Only the person signed in with the email you registered can approve.

**4c. Poll** the `token_endpoint` every `interval` seconds:

```http
POST <token_endpoint>
Content-Type: application/x-www-form-urlencoded

grant_type=urn:workos:agent-auth:grant-type:claim&claim_token=clm_…
```

| Answer | What to do |
|---|---|
| `authorization_pending` | Keep waiting. |
| `slow_down` | Add 5 seconds to your interval. |
| `expired_token` | The code expired or was mistyped 5 times. POST the `claim_endpoint` again with the same `claim_token` and email, and give the user the NEW link and code. |
| `access_denied` | The user declined. Stop. |
| `invalid_grant` | The registration is gone. Restart at Step 3. |
| 200 | Done: `{access_token, token_type, expires_in: 3600, scope, identity_assertion, assertion_expires}`. |

If you lose the 200 response, polling again returns it for 10 minutes.

Save `identity_assertion` (your 30-day refresh credential) and the `access_token` to your private credentials file. Never print them.

## Step 5 — Read your scope

- `research:read research:write research:delete` — the user has a team. Go to Step 6.
- `account:onboard` — a new user with no team yet. Your token works only on `/agent/onboarding`. Set their team up with them in chat ([agents.md → Onboarding](https://www.useprobe.ai/agents.md#onboarding)), then exchange again (Step 6): the new token adds the research scopes.

## Step 6 — Exchange and refresh (RFC 7523)

```http
POST <token_endpoint>
Content-Type: application/x-www-form-urlencoded

grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer&assertion=<identity_assertion>
```

Returns `{access_token, token_type: "Bearer", expires_in: 3600, scope}`. Optional `scope=research:read` narrows a token; you can never widen one. Re-use the same assertion for every refresh until it expires (30 days). At most 30 tokens per hour: keep the one you hold until it expires.

`invalid_grant` means the assertion expired, was superseded or revoked, or the user left the team: restart at Step 3. `invalid_scope` before the claim means: finish Step 4.

## Step 7 — Use the token

```http
GET https://api.research.prbe.ai/v1/me
Authorization: Bearer <access_token>
```

The same token works on the REST API (`/v1/...`) and the hosted MCP server (`https://mcp.research.prbe.ai/mcp`). On a 401, exchange once (Step 6) and retry; if that fails, restart at Step 1.

Your access: what the user's own `probe` key can do. Read, write and delete the team's research as this user, with their role, publish it, and use the dashboard assistant's API. You cannot administer the team or mint API tokens (no key can), and session capture comes from installing Probe on the machine ([agents.md → Install](https://www.useprobe.ai/agents.md#install)).

## Errors

Registration (`identity_endpoint`, `claim_endpoint`) answers `{"error": "…", "error_description": "…"}`; the token endpoint uses OAuth vocabulary.

| Code | Where | What to do |
|---|---|---|
| `issuer_not_enabled` | identity | ID-JAG is not supported. Use `service_auth` or `anonymous`. |
| `invalid_request` | identity, claim | Fix the JSON body. |
| `invalid_login_hint` | identity, claim | Send the user's email address. |
| `invalid_claim_token` (401) | claim | Restart at Step 3. |
| `claimed_or_in_flight` (409) | claim | Already claimed: poll the claim grant, or exchange your assertion. |
| `claim_expired` (410) | claim | The 24-hour window closed. Restart at Step 3. |
| `rate_limited` (429) | any | Wait `Retry-After` seconds. |
| `authorization_pending`, `slow_down`, `expired_token`, `access_denied` | token (claim grant) | See Step 4c. |
| `invalid_grant` | token | Restart at Step 3. |
| `invalid_scope` | token | Finish the claim, or ask only for scopes you hold. |
| `invalid_target` | token | Drop `resource`, or use the API or MCP URL. |
| `unsupported_grant_type` | token | Use one of the two grants above. |

## Revocation

- **One access token:** `POST <revocation_endpoint>` with `token=<access_token>`. Your assertion still works.
- **Log out entirely:** `POST <revocation_endpoint>` with `token=<identity_assertion>`. Everything you hold stops working.
- **The user** can revoke you in Settings → AI agents, and is emailed when you connect. You see it as `invalid_grant`.

The revocation endpoint always answers 200 with an empty body.

## Scopes

| Scope | What it lets you do |
|---|---|
| `research:read` | View projects, experiments, runs, metrics, files and notes. |
| `research:write` | Create and update research data, and publish it. |
| `research:delete` | Delete research data. Projects, experiments, runs and groups go to a trash Probe can restore for 21 days; files are deleted for good. |
| `account:onboard` | Set up a new user's account: create or join a team, invite teammates, finish onboarding. Granted only when the user has no team yet; it ends when onboarding finishes. |

## Rules

- Never ask the user for a password, an API key or an integration secret (such as a W&B key) in chat. Give them the browser link instead: chats can be recorded.
- Keep tokens, the claim token and the identity assertion only in your private credentials file (mode `600`, outside any repository). Never print them, commit them, or put them in a note or anything you show.
- The user approves you; you never complete the claim page yourself.
- Ask the user in chat before you delete anything or publish research publicly, every time. Deleted files cannot be restored, and published research is visible to anyone with the link.

Privacy: https://www.useprobe.ai/privacy/ · Contact: support@prbe.ai
