# Apartment Fax agent authentication

You are an agent. This service supports **anonymous agentic registration**: discover -> register -> use the credential -> revoke it when you are done. Follow the steps in order.

Two hosts matter here. `https://apartmentfaxnyc.com` is the site and serves this document. `https://api.apartmentfaxnyc.com` is the resource server that holds the API and the discovery documents.

Most of what Apartment Fax exposes to agents needs **no credential at all**. The MCP server at `https://api.apartmentfaxnyc.com/mcp`, the address resolver, and the free public-record preview are open. Register only when you need to buy and read a full report.

## Step 1: Discover

Discovery is two hops. A `401` from this API points at the first one:

```http
HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer resource_metadata="https://api.apartmentfaxnyc.com/.well-known/oauth-protected-resource"
```

### 1a. Protected Resource Metadata (RFC 9728)

```http
GET https://api.apartmentfaxnyc.com/.well-known/oauth-protected-resource
```

```json
{
  "resource": "https://api.apartmentfaxnyc.com/",
  "resource_name": "Apartment Fax",
  "resource_documentation": "https://apartmentfaxnyc.com/developers",
  "authorization_servers": ["https://api.apartmentfaxnyc.com"],
  "bearer_methods_supported": ["header"]
}
```

`scopes_supported` is absent on purpose. Keys here are not scoped, and listing scopes the server never enforces would be a promise you could not rely on. What a key can do is fixed: buy reports against its own balance, and read the reports it bought.

### 1b. Authorization Server Metadata (RFC 8414)

```http
GET https://api.apartmentfaxnyc.com/.well-known/oauth-authorization-server
```

The `agent_auth` block is the part you want:

```json
{
  "issuer": "https://api.apartmentfaxnyc.com",
  "grant_types_supported": [],
  "revocation_endpoint": "https://api.apartmentfaxnyc.com/v1/agent/revoke",
  "agent_auth": {
    "skill": "https://apartmentfaxnyc.com/auth.md",
    "identity_endpoint": "https://api.apartmentfaxnyc.com/v1/agent/register",
    "register_uri": "https://api.apartmentfaxnyc.com/v1/agent/register",
    "revocation_uri": "https://api.apartmentfaxnyc.com/v1/agent/revoke",
    "identity_types_supported": ["anonymous"],
    "anonymous": { "credential_types_supported": ["bearer_api_key"] }
  }
}
```

`identity_endpoint` and `register_uri` are the same URL under two names: `register_uri` is the pre-v0.2.0 field name, kept as an alias for readers that still key on it.

`grant_types_supported` is empty and there is no `token_endpoint`, because this service runs no OAuth grant. Registration returns the bearer credential directly.

## Step 2: Pick a method

`identity_types_supported` lists exactly one type, so there is nothing to weigh:

| Method | Supported | Notes |
| --- | --- | --- |
| `anonymous` | **Yes** | The only flow. No human, no email, no provider. Go to Step 3. |
| `identity_assertion` (`id-jag`) | No | This service verifies no `urn:ietf:params:oauth:token-type:id-jag` assertion, keeps no agent-provider trust list, and reads no `"assertion_type": "verified_email"`. It has no user accounts for an assertion to map onto. |
| `service_auth` | No | Same reason. |

Do not attempt an assertion-based register. It will be rejected. Anonymous registration is not a fallback here, it is the whole surface.

## Step 3: Register

`register_uri: https://api.apartmentfaxnyc.com/v1/agent/register`

```http
POST https://api.apartmentfaxnyc.com/v1/agent/register
Content-Type: application/json

{ "type": "anonymous", "name": "my-agent" }
```

`name` is an optional label for your own logs. The server generates the stored name itself and ignores yours as an identifier, because a client-chosen name would be a client-chosen tenant.

`201 Created`:

```json
{
  "accountId": "acct_9f2c...",
  "keyId": "key_4a71...",
  "secret": "af_live_...",
  "balance": 0,
  "documentation": "https://apartmentfaxnyc.com/auth.md"
}
```

**`secret` is returned in this response and in no other, ever.** Only its hash is stored. Lose it and the only repair is to revoke the key and register again.

The account opens with **`balance: 0`**. The credential buys nothing until credits are purchased for it, which is exactly why registration can be open in the first place. Nothing you obtain here grants access to anyone else's report.

## Step 4: Claim ceremony

**Not applicable.** Anonymous registration completes in one call and hands you the credential in Step 3, so there is no `claim_token` to redeem, no `claim_uri` to poll, and no user-facing confirmation code. `claim_uri` is absent from `agent_auth` for that reason rather than by omission. Skip to Step 5.

## Step 5: Use the credential

Send it as a bearer token. No exchange step, no refresh:

```http
GET https://api.apartmentfaxnyc.com/api/balance
Authorization: Bearer af_live_...
```

The credential identifies an `acct_` principal. Every ownership check treats it exactly like a signed-in user, scoped to its own reports: a key cannot read a web customer's report, and a web customer cannot read a key's.

Full endpoint reference: <https://apartmentfaxnyc.com/developers> and <https://api.apartmentfaxnyc.com/openapi.json>.

## Errors

| Status | Meaning | What to do |
| --- | --- | --- |
| `401` | No credential, or a revoked or unknown one. Carries `WWW-Authenticate` with the PRM URL. | Register (Step 3). Do not retry the same key. |
| `403` | Valid credential, wrong principal for this route. | Stop. A different credential type is needed, not a retry. |
| `402` / balance `0` | Authenticated but out of credits. | Credits must be purchased for the account. Retrying will not help. |
| `429` | Rate limited. Read `RateLimit-Reset` and `Retry-After`. | Back off for the interval given. |
| `503` | Accounts are unavailable. | Transient. Retry with backoff. |

The assertion-validation errors named in the auth.md spec (`invalid_signature`, `replay_detected`, `audience_mismatch`, `credential_expired`) are **never emitted by this service**. They belong to the ID-JAG flow of Step 2, which is not implemented. An Apartment Fax credential does not expire on a clock; it is valid until revoked.

## Revocation

`revocation_uri: https://api.apartmentfaxnyc.com/v1/agent/revoke`

Revoke the key you are holding by presenting it:

```http
POST https://api.apartmentfaxnyc.com/v1/agent/revoke
Authorization: Bearer af_live_...
```

Revocation is permanent and takes effect on the next request, not eventually: verification reads the table every time rather than sitting behind a cache.

There is no session to `logout` of and no `jwt` to let expire. The credential is an opaque API key, not a signed token, so revoking it at `revocation_uri` is the only thing that stops it. Revoking a key does not close its account or refund its balance.
