# auth.md

You are an agent. This service supports **agentic registration**: discover →
register → exchange for an `access_token` → call the API. Follow the steps in
order.

Read this first, because it shortens the rest: **this is a public sandbox.** The
credentials are published, shared by every caller, and guard nothing. Every
response is mock data. So the registration step below is a formality that hands
you the same credentials the landing page prints, there is no identity to claim,
and there is nothing to revoke. The flow is spelled out anyway, because the point
of this sandbox is to be a worked example of the flow.

Host used below: `https://camara.jaime.win` is both the resource server (the API
you want to call) and the authorization server.

## Step 1 — Discover

Discovery is two hops. You may already have done the first one: the `401` that
sent you here carries the protected resource metadata URL.

```http
HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer realm="camara-sandbox", resource_metadata="https://camara.jaime.win/.well-known/oauth-protected-resource"
```

### 1a. Protected resource metadata (RFC 9728)

```http
GET /.well-known/oauth-protected-resource
```

```json
{
  "resource": "https://camara.jaime.win",
  "resource_name": "CAMARA Sandbox",
  "authorization_servers": ["https://camara.jaime.win"],
  "scopes_supported": ["sim-swap:check", "..."],
  "bearer_methods_supported": ["header"]
}
```

- `resource` — the canonical URL of the API you are calling.
- `authorization_servers` — where the `agent_auth` block lives. Here it is the
  same origin.
- `scopes_supported` — the scopes the resource server understands. The token you
  get in Step 3 carries all of them.
- `bearer_methods_supported` — `header`, so send `Authorization: Bearer …`.

### 1b. Authorization server metadata (RFC 8414)

```http
GET /.well-known/oauth-authorization-server
```

```json
{
  "issuer": "https://camara.jaime.win",
  "token_endpoint": "https://camara.jaime.win/oauth2/token",
  "token_endpoint_auth_methods_supported": ["client_secret_post"],
  "grant_types_supported": ["client_credentials"],
  "agent_auth": {
    "skill": "https://camara.jaime.win/auth.md",
    "register_uri": "https://camara.jaime.win/agent/identity",
    "claim_uri": "https://camara.jaime.win/agent/identity/claim",
    "identity_types_supported": ["anonymous"],
    "anonymous": {
      "credential_types_supported": ["client_secret_post"],
      "claim_uri": "https://camara.jaime.win/agent/identity/claim",
      "claim_required": false
    },
    "events_supported": []
  }
}
```

Validate the `iss` of anything this server signs against `issuer`, and check
that `issuer` matches the `authorization_servers` entry you followed from 1a.

## Step 2 — Pick a method

There is one: **`anonymous`**. `identity_types_supported` lists only that.

No `identity_assertion` and no `service_auth`, because there is no user identity
worth asserting to a sandbox and no account to bind it to. If you hold an ID-JAG,
do not bother minting one for this audience; it will not be accepted.

## Step 3 — Register

```http
POST /agent/identity
Content-Type: application/json

{"identity_type": "anonymous"}
```

```json
{
  "identity_type": "anonymous",
  "client_id": "sandbox",
  "client_secret": "sandbox",
  "token_endpoint": "https://camara.jaime.win/oauth2/token",
  "grant_types_supported": ["client_credentials"],
  "claim_required": false,
  "claim_uri": "https://camara.jaime.win/agent/identity/claim"
}
```

The credentials are not yours and not secret: every caller gets `sandbox` /
`sandbox`. You may skip this request entirely and go straight to Step 4 with
those values. No consent gate is needed, because no user identity is being
asserted and no user data is at stake.

Any `identity_type` other than `anonymous` returns `400
IDENTITY_TYPE_NOT_SUPPORTED` with the supported list.

## Step 4 — Exchange for an access token

```http
POST /oauth2/token
Content-Type: application/x-www-form-urlencoded

grant_type=client_credentials&client_id=sandbox&client_secret=sandbox
```

```json
{
  "access_token": "…",
  "token_type": "Bearer",
  "expires_in": 3600,
  "scope": "sim-swap:check sim-swap:retrieve-date …",
  "_links": {
    "sim-swap:check": {
      "href": "https://camara.jaime.win/sim-swap/v1/check",
      "method": "POST",
      "title": "Check if SIM was recently swapped"
    }
  }
}
```

The `_links` map is the useful part: it lists every operation the token unlocks,
with method and purpose, so you can navigate from the token instead of from
documentation.

## Step 5 — Call the API

```http
POST /sim-swap/v1/check
Authorization: Bearer <access_token>
Content-Type: application/json

{"phoneNumber": "+34600000001", "maxAge": 240}
```

Test numbers, request bodies and response shapes for all nine operations are in
[SKILL.md](https://camara.jaime.win/SKILL.md). Errors are RFC 9457
`application/problem+json`, and each one names the next useful step.

## Claiming and revocation

`POST /agent/identity/claim` answers `{"status": "not_required"}`. One public
credential is issued to everybody, so there is no identity to transfer to an
owner and nobody to notify.

There is no `revocation_endpoint`, and `events_supported` is empty. Revoking a
published credential would mean nothing, so this service does not advertise an
endpoint it cannot honour. Tokens expire on their own after `expires_in`
(3600 seconds).

## If you are reading this to copy it

The shape above follows [auth.md](https://github.com/workos/auth.md). A real
service would support `identity_assertion` with an audience-bound ID-JAG, gate
Step 3 behind user consent showing `resource_name` and `resource_logo_uri`, and
publish both a `revocation_endpoint` and a revocation event in
`events_supported`. This sandbox omits all three on purpose, and says so, rather
than stubbing them.
