# auth.md

You are an agent. This file explains how to get credentials for Linkbreakers. Follow the steps in order.

Three methods exist: a workspace API token a person gives you (Method A), OAuth 2.1 for the MCP server (Method B), and agentic self-registration (Method C), which needs nobody: you register, get a workspace of your own and a token, and a person can claim the workspace later. Identity types accepted at `identity_endpoint`: `anonymous`. Not accepted: `service_auth`, `identity_assertion` (no ID-JAG issuer, `urn:ietf:params:oauth:token-type:id-jag`, is trusted).

## Step 1: Discover

- REST API: `https://api.linkbreakers.com`. OpenAPI description at https://linkbreakers.com/openapi.json, catalog at https://linkbreakers.com/.well-known/api-catalog.
- REST API protected resource metadata (RFC 9728): https://api.linkbreakers.com/.well-known/oauth-protected-resource.
- MCP server: `https://mcp.linkbreakers.com`. An unauthenticated request returns `401` with `WWW-Authenticate: Bearer resource_metadata="https://mcp.linkbreakers.com/.well-known/oauth-protected-resource"`.
- MCP protected resource metadata: https://mcp.linkbreakers.com/.well-known/oauth-protected-resource
- Authorization server metadata (RFC 8414): https://mcp.linkbreakers.com/.well-known/oauth-authorization-server
- The authorization server metadata carries the `agent_auth` block: `identity_endpoint` https://mcp.linkbreakers.com/agent/identity, `claim_endpoint` https://mcp.linkbreakers.com/agent/identity/claim, `identity_types_supported` `anonymous`, `skill` https://linkbreakers.com/auth.md. There is no `events_endpoint`: no identity provider can revoke a registration here.

## Step 2: Pick a method

1. **Method A, workspace API token.** A person already has a workspace and gives you a token. Use it for the REST API, an SDK or the CLI.
2. **Method B, OAuth 2.1 for MCP.** You are an MCP client and a person can sign in and consent in a browser. Authorization code with PKCE (S256) and dynamic client registration.
3. **Method C, anonymous self-registration.** You have no user identity, or nobody is around yet. You get a workspace of your own on the free plan, at once. A person can take it over later with the claim ceremony; until then it lasts 24 hours.

### Without credentials

These need no token and no registration:

- `GET https://api.linkbreakers.com/v1/api-scopes`: List API scopes
- `GET https://api.linkbreakers.com/v1/pricing/plans`: List pricing plans
- `GET https://api.linkbreakers.com/v1/public/page-theme-templates`: List page theme templates
- `GET https://api.linkbreakers.com/v1/public/qrcode-templates`: List QR code templates
- MCP server `https://linkbreakers.com/mcp`: Search and read the Linkbreakers help center and developer docs. No sign-in required.

## Step 3: Register

- Method A: ask the workspace owner to create a token at https://app.linkbreakers.com/workspace/dashboard/api-tokens (Workspace tokens) and hand it to you. Guide: https://linkbreakers.com/help/article/how-to-use-the-linkbreakers-api.
- Method B: register your client with `POST https://mcp.linkbreakers.com/oauth/register` (RFC 7591), sending `redirect_uris` and `token_endpoint_auth_method: "none"`.
- Method C: `POST https://mcp.linkbreakers.com/agent/identity` with `Content-Type: application/json` and the body `{"type":"anonymous"}`. No consent step: there is no user identity to assert.

  The 200 response carries:

  - `registration_id`, `registration_type` (`anonymous`) and `workspace_id`, the workspace created for you.
  - `access_token` with `token_type` `Bearer` and `scope`: a token you can use right away. It holds `pre_claim_scopes`.
  - `identity_assertion` and `assertion_expires`: exchange it for an access token at Step 5.
  - `claim_url`, `claim_token` and `claim_token_expires`: for Step 4. `claim_token` is returned once; keep it in memory.
  - `post_claim_scopes`: what you may do once a person has claimed the workspace.

  The workspace is on the free plan, with its limits, including the monthly API allowance. Registrations are limited per client address.
- `service_auth` and `identity_assertion` are refused at `identity_endpoint` with `service_auth_not_enabled` / `issuer_not_enabled`.

## Step 4: Claim ceremony

Method C only, and optional: do it when a person wants to own the workspace you built. Methods A and B need none, since a person grants them in the first place.

1. `POST https://mcp.linkbreakers.com/agent/identity/claim` with `{"claim_token":"clm_...","email":"person@example.com"}`. We email that address a claim link and a 6-digit code. The response has `registration_id`, `claim_attempt_id`, `"status": "initiated"`, `expires_at` and a `claim_attempt` block with `"delivery": "email"`, `email`, `expires_in` and `interval`. Unlike the profile, it has no `user_code` or `verification_uri`: they reach the person only by email, so completing the claim proves they own the address and verifies it. Use the person's real address.
2. Tell the person to open the claim email, for example: "I emailed you a link to claim the Linkbreakers workspace I set up; open it within 24 hours." On that page they enter the code from the email and save a password, or sign in with Google or Microsoft, for that address. Someone who already has an account signs in with it and the workspace joins their account.
3. Poll `POST https://mcp.linkbreakers.com/oauth/token` with `grant_type=urn:workos:agent-auth:grant-type:claim` and `claim_token`, every `interval` seconds. While waiting you get `authorization_pending`; polling faster gets `slow_down`. On success you get a new `access_token` holding the post-claim scopes, a new `identity_assertion` that names the person, and `assertion_expires`. This is handed out once.
4. If the email did not arrive, call the claim endpoint again with the same `claim_token` and the corrected email: each call sends a new email and voids the previous one, up to 3 a day per address (then `429` `rate_limited`). If that answers `410` `claim_expired`, the 24-hour window closed and the workspace is gone: register again.

After the claim, your pre-claim access token keeps working in the claimed workspace with its pre-claim scopes, and the pre-claim identity assertion is superseded.

## Step 5: Exchange

- Method A: nothing to exchange. The dashboard token is the bearer token.
- Method B: send the user to `https://mcp.linkbreakers.com/oauth/authorize` with `response_type=code`, your `client_id`, `redirect_uri`, `code_challenge` and `code_challenge_method=S256` and `scope=mcp:tools links:read links:write workflows:read workflows:write qrcodes:read qrcodes:write page_themes:read page_themes:write media:read media:write domains:read domains:write visitors:read visitors:write analytics:read analytics:write webhooks:read webhooks:write integrations:read integrations:write workspace:read workspace:write members:read members:write tokens:read tokens:write billing:write assistant:read assistant:write`. Then exchange the code at `https://mcp.linkbreakers.com/oauth/token` with `grant_type=authorization_code` and your `code_verifier`. Refresh with `grant_type=refresh_token`.
- Method C: `POST https://mcp.linkbreakers.com/oauth/token` with `grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer`, `assertion=<identity_assertion>` and optionally `resource` (`https://api.linkbreakers.com` or `https://mcp.linkbreakers.com`), form-encoded (RFC 7523). The response is `access_token`, `token_type` and `scope`. The token does not expire on a timer; it ends when it is revoked or the unclaimed workspace is removed. The same assertion can be exchanged again until `assertion_expires`; there is no refresh token.

## Step 6: Use the access_token

- Method A: `Authorization: Bearer <token>` on every request to `https://api.linkbreakers.com`. The same token works with the SDKs and `linkbreakers auth set-token` in the CLI.
- Method B: `Authorization: Bearer <access_token>` on requests to `https://mcp.linkbreakers.com`.
- Method C: `Authorization: Bearer <access_token>` on the REST API and the MCP server `https://mcp.linkbreakers.com`. A call outside your scopes answers `403` `INSUFFICIENT_SCOPE`.

### Scopes

- REST API scopes (from its protected resource metadata): `links:read`, `links:write`, `workflows:read`, `workflows:write`, `qrcodes:read`, `qrcodes:write`, `page_themes:read`, `page_themes:write`, `media:read`, `media:write`, `domains:read`, `domains:write`, `visitors:read`, `visitors:write`, `analytics:read`, `analytics:write`, `webhooks:read`, `webhooks:write`, `integrations:read`, `integrations:write`, `workspace:read`, `workspace:write`, `members:read`, `members:write`, `tokens:read`, `tokens:write`, `billing:write`, `assistant:read`, `assistant:write`
- MCP server scopes: `mcp:tools`, `links:read`, `links:write`, `workflows:read`, `workflows:write`, `qrcodes:read`, `qrcodes:write`, `page_themes:read`, `page_themes:write`, `media:read`, `media:write`, `domains:read`, `domains:write`, `visitors:read`, `visitors:write`, `analytics:read`, `analytics:write`, `webhooks:read`, `webhooks:write`, `integrations:read`, `integrations:write`, `workspace:read`, `workspace:write`, `members:read`, `members:write`, `tokens:read`, `tokens:write`, `billing:write`, `assistant:read`, `assistant:write`
- Method C: the registration response lists `pre_claim_scopes` and `post_claim_scopes`.

## Sandbox (test credentials)

Sandbox workspaces: separate, API-only test workspaces created from a live workspace, reached with their own lb_test_ tokens, never billed, and deleted after 30 days.

- Create: `POST https://api.linkbreakers.com/v1/sandboxes` with a live workspace token (scope `tokens:write`). The response carries a token starting with `lb_test_`, shown once. List with `GET https://api.linkbreakers.com/v1/sandboxes`.
- The `lb_test_` token works on the REST API, the SDKs, the CLI and the MCP server, and only reaches the sandbox. A `lb_test_` prefix on a live token is refused.
- Never billed and not counted toward the plan. Monthly caps: 1000 API calls, 500 counted scans, 50 links.
- Deleted with all its data 30 days after creation; at most 3 per workspace.
- Guide: https://linkbreakers.com/help/article/sandbox-test-environment

## Errors

| Response | Where | What to do |
| --- | --- | --- |
| `401` with `WWW-Authenticate` | MCP server | Start again at Step 1, or refresh the access token |
| `401` `AUTH_REQUIRED` | REST API | No token was sent. Add the `Authorization` header |
| `401` `AUTH_INVALID_FORMAT` | REST API | The header is not `Bearer <token>` |
| `401` `AUTH_TOKEN_INVALID` | REST API | The token is wrong or revoked. Ask the workspace owner for a new one |
| `403` `PUBLISHABLE_KEY_INSUFFICIENT_PERMISSIONS` | REST API | A publishable key only reaches visitor identification endpoints. Use a secret workspace token |
| `403` `INSUFFICIENT_SCOPE` | REST API | The token lacks the scope the endpoint needs |
| `429` `RATE_LIMIT_EXCEEDED` | REST API | Back off and retry later |
| `429` `MAX_PUBLIC_API_QUERIED_REACHED` | REST API | The plan's API allowance is used up. See https://linkbreakers.com/pricing.md |
| `400` `invalid_request` | `identity_endpoint`, `claim_endpoint` | Fix the body: `{"type":"anonymous"}`, or `claim_token` and a valid `email` |
| `400` `service_auth_not_enabled` | `identity_endpoint` | Register with `anonymous` |
| `400` `issuer_not_enabled` | `identity_endpoint` | Register with `anonymous` |
| `400` `invalid_claim_token` | `claim_endpoint` | The `claim_token` is wrong. Register again |
| `409` `claimed_or_in_flight` | `claim_endpoint` | Already claimed, or a person is completing it now. Poll the claim grant |
| `410` `claim_expired` | `claim_endpoint` | The 24-hour window closed. Register again |
| `429` `rate_limited` | `identity_endpoint`, `claim_endpoint` | Too many registrations or ceremonies. Honor `Retry-After` |
| `400` `authorization_pending` | Token endpoint, claim grant | The person has not finished. Retry after `interval` |
| `400` `slow_down` | Token endpoint, claim grant | Add 5 seconds to your interval |
| `400` `expired_token` | Token endpoint, claim grant | Start a new ceremony with the claim endpoint |
| `400` `invalid_target` | Token endpoint | `resource` is not one of this service's resources |
| `400` `invalid_grant` | Token endpoint | The code, refresh token, assertion or claim token is expired, revoked, superseded or used. Method B: restart it. Method C: register again |
| `400` `unsupported_grant_type` | Token endpoint | Use a grant listed in the metadata's `grant_types_supported` |

## Revocation

- Method A: the workspace owner deletes the token at https://app.linkbreakers.com/workspace/dashboard/api-tokens. Requests then return `401` with `AUTH_TOKEN_INVALID`.
- Method B: revoke with `POST https://mcp.linkbreakers.com/oauth/revoke` (RFC 7009, form-encoded `token=...`), or the user disconnects the client.
- Method C: your token is a workspace token named "AI agent". You can revoke it with `POST https://mcp.linkbreakers.com/oauth/revoke`; the workspace owner can delete it in the dashboard. Either way it is final: exchanging the assertion again answers `invalid_grant`, and you need a new registration. An unclaimed workspace and its tokens are removed when the 24-hour window closes.

---

More: https://linkbreakers.com/llms.txt and https://linkbreakers.com/?mode=agent
