# auth.md

You are an agent. This service supports agentic registration: discover → register → exchange for an access_token → call the API → revoke.

**Proquiro's public API is open.** Every endpoint in <https://proquiro.com/api/openapi.json> answers unauthenticated callers, and the MCP server at <https://proquiro.com/mcp> needs no credential. Registering only gives you a stable identity to present and a credential you can revoke. To just call an endpoint, go to Step 6 and omit the `Authorization` header.

Resource server (the API): `https://proquiro.com/api/` · Authorization server: `https://proquiro.com`. Same origin.

## Step 1 — Discover

Two hops.

**1a.** `GET https://proquiro.com/.well-known/oauth-protected-resource` — RFC 9728 metadata: `resource` (`https://proquiro.com/api/`, the `aud` on your assertion), `resource_name`, `authorization_servers` (where the `agent_auth` block lives), `scopes_supported` (`api.read`), `bearer_methods_supported` (`header` = `Authorization: Bearer …`).

You can also reach it from a `WWW-Authenticate: Bearer resource_metadata="…"` header, returned on a 401 when you present an **invalid** token. With no `Authorization` header you get 200, because anonymous access is allowed.

**1b.** `GET https://proquiro.com/.well-known/oauth-authorization-server` — RFC 8414 metadata: `token_endpoint`, `revocation_endpoint`, `grant_types_supported` (one grant; no authorization-code flow, no PKCE, no refresh_token), plus:

```json
{
  "agent_auth": {
    "skill": "https://proquiro.com/auth.md",
    "register_uri": "https://proquiro.com/agent/identity",
    "identity_endpoint": "https://proquiro.com/agent/identity",
    "identity_types_supported": ["anonymous"],
    "anonymous": { "credential_types_supported": ["urn:ietf:params:oauth:token-type:access_token"] }
  }
}
```

`register_uri` and `identity_endpoint` are the same URL under both spellings. There is **no `claim_endpoint` and no `events_endpoint`** — their absence signals that this service runs no claim ceremony and accepts no provider-pushed Security Event Tokens. Do not probe for them.

## Step 2 — Pick a method

Whatever you hold, use `anonymous` — it is the only type accepted. `identity_assertion` returns `issuer_not_enabled` (no agent-provider trust list here) and `service_auth` returns `service_auth_not_enabled` (no user accounts on this domain — Proquiro product accounts live in the Proquiro application, so no signed-in user exists here to confirm a claim code).

## Step 3 — Register

```http
POST https://proquiro.com/agent/identity
Content-Type: application/json

{ "type": "anonymous", "client": { "name": "your-agent", "version": "1.0.0" } }
```

`client` is optional; `name` and `version` are recorded (truncated at 128 chars). Response 200 carries `registration_id`, `registration_type`, `identity_assertion` (a service-signed JWT), `assertion_expires`, `pre_claim_scopes` (`["api.read"]`), `token_endpoint`, and `claim_supported: false`.

`claim_supported: false` is explicit — `pre_claim_scopes` are the only scopes this registration will ever hold, and no `claim_token` is issued. Keep `identity_assertion`; it is your refresh path. Assertion lives 15 minutes, registration 30 days.

## Step 4 — Claim ceremony

**Not offered.** There is no `/agent/identity/claim`, and `agent_auth` advertises no `claim_endpoint`. Skip to Step 5.

## Step 5 — Exchange the assertion

RFC 7523 JWT-bearer at the `token_endpoint`:

```http
POST https://proquiro.com/oauth2/token
Content-Type: application/x-www-form-urlencoded

grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer&assertion=<identity_assertion>&resource=https://proquiro.com/api/
```

`resource` is optional; if sent it must be `https://proquiro.com/api/`. A JSON body with the same keys also works. Response 200: `{ "access_token": "pqa_…", "token_type": "Bearer", "expires_in": 3600, "scope": "api.read" }`.

No refresh_token. Re-post the same assertion for another token until it expires, then register again.

## Step 6 — Use the access_token

```http
GET https://proquiro.com/api/v1/health
Authorization: Bearer pqa_…
```

A valid token adds an `agent` block to the response echoing your `registration_id` and scope. Everything else behaves identically with or without the header. Rate limits are per IP (300/min across `/api/*`; 5/hour on `/api/v1/demo-request`) and a token does not raise them. Writes accept an `Idempotency-Key` header so retries are safe.

**Refresh:** on expiry, repeat Step 5 with the same assertion. On `invalid_grant`, restart at Step 3.

## Step 7 — Revoke

```http
POST https://proquiro.com/oauth2/revoke
Content-Type: application/x-www-form-urlencoded

token=pqa_…&token_type_hint=access_token
```

Returns 200 with an empty body, and is idempotent — revoking an unknown token is not an error (RFC 7009 §2.2). Revocation takes effect on the next request. Your assertion survives; re-run Step 5 for a fresh token. There is no registration-layer revocation channel, because no external provider ever vouched for this registration.

## Errors

| Code                       | Where                                                | What to do                                                                                           |
| -------------------------- | ---------------------------------------------------- | ---------------------------------------------------------------------------------------------------- |
| `invalid_request`          | `/agent/identity`                                    | Body is not a JSON object, or `type` is undocumented. Fix the body.                                  |
| `issuer_not_enabled`       | `/agent/identity`                                    | You sent `identity_assertion`. Use `anonymous`.                                                      |
| `service_auth_not_enabled` | `/agent/identity`                                    | You sent `service_auth`. Use `anonymous`.                                                            |
| `invalid_request`          | `/oauth2/token`                                      | Missing `grant_type` or `assertion`.                                                                 |
| `unsupported_grant_type`   | `/oauth2/token`                                      | Only the JWT-bearer grant exists; the claim grant is not implemented.                                |
| `invalid_target`           | `/oauth2/token`                                      | `resource` is not `https://proquiro.com/api/`.                                                       |
| `invalid_grant`            | `/oauth2/token`                                      | Assertion expired, malformed, or its registration lapsed. Restart at Step 3.                         |
| `temporarily_unavailable`  | `/agent/identity`, `/oauth2/token`, `/oauth2/revoke` | Credential storage briefly unreachable. Back off — or call the API anonymously, which is unaffected. |
| `INVALID_TOKEN` (401)      | any `/api/*`                                         | Token invalid, expired, or revoked. Drop the header to continue anonymously, or register again.      |

Retry policy: 5xx → exponential backoff, same request; these carry `Retry-After`. 4xx → do not resend the same payload; act on the table.

`temporarily_unavailable` from `/oauth2/revoke` means the token was **not** revoked and stays valid until a retry succeeds — unlike the 200, which is final.

## Scopes

`api.read` — read access to the documented public API. Identical to what an anonymous caller already has.

## Pointers

- API reference — <https://proquiro.com/api/openapi.json> · markdown at <https://proquiro.com/api/docs>
- Developer hub — <https://proquiro.com/developers> · auth guide <https://proquiro.com/developers/auth>
- MCP server (no credential required) — <https://proquiro.com/mcp>
- Pricing <https://proquiro.com/pricing> · Terms <https://proquiro.com/terms> · Privacy <https://proquiro.com/privacy> · AI policy <https://proquiro.com/ai-policy>
- Deprecation policy — <https://proquiro.com/developers/deprecation-policy>
- Integration problems — <https://proquiro.com/contact>
