# auth.md

Not an agent? You might be looking for https://docs.api.casafari.com/.

You are an agent. This document tells you how to obtain a credential for Casafari's property data. There are two surfaces with different credentials: the **MCP server** (OAuth 2.1) and the **REST API** (email and password login). Follow the steps for the surface you need, in order; do not skip ahead.

A credential does not grant data access by itself. Access is bound to a Casafari account with an API or MCP subscription, and a token carries exactly the rights of that account: the tools and endpoints it may call. If the user has no Casafari account, stop and tell them to obtain one; nothing below will work without it.

## MCP server

Endpoint: `https://mcp.casafari.com/` over Streamable HTTP. The server is an OAuth 2.1 resource server.

Supported registration is **user-authorized**: you register a client, the user signs in to their Casafari account in a browser, and the token you receive acts for that user. There is no anonymous registration: a self-registered client cannot use the client credentials grant (see [Unattended agents](#unattended-agents)).

### Step 1 — Discover

Discovery is two hops. A request without a token answers `401` with a `WWW-Authenticate` header that points at the protected resource metadata:

```http
HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer error="invalid_token", resource_metadata="https://mcp.casafari.com/.well-known/oauth-protected-resource"
```

#### 1a. Fetch the Protected Resource Metadata

```http
GET https://mcp.casafari.com/.well-known/oauth-protected-resource
```

Read the live document rather than a copy of it. The fields you need:

- `resource` — the canonical URL of the MCP server. Send it, exactly as stated there, as the `resource` parameter in every authorization and token request; tokens are bound to it as their audience.
- `authorization_servers` — the OAuth authorization server to register with (step 1b).
- `scopes_supported` — empty on purpose: Casafari does not use scopes. Rights are per tool and resolved from the account on every call.
- `resource_documentation` — the MCP tool reference.

#### 1b. Fetch the Authorization Server Metadata

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

Read `registration_endpoint`, `authorization_endpoint` and `token_endpoint` from it, and take `grant_types_supported`, `code_challenge_methods_supported` and `token_endpoint_auth_methods_supported` from the same document rather than from this guide: the authorization server owns those lists.

### Step 2 — Register

Skip this if you already hold a `client_id` for this authorization server.

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

{
  "client_name": "<your agent name>",
  "redirect_uris": ["<your callback URL>"],
  "grant_types": ["authorization_code", "refresh_token"],
  "token_endpoint_auth_method": "none"
}
```

Redirect URIs must be `https`, or `http` on a loopback host, or a private-use scheme; no fragments. `grant_types` may contain only `authorization_code` and `refresh_token`; anything else is rejected with `400 invalid_client_metadata`.

Response (201):

```json
{
  "client_id": "...",
  "client_id_issued_at": 1700000000,
  "token_endpoint_auth_method": "none",
  "grant_types": ["authorization_code", "refresh_token"],
  "response_types": ["code"],
  "redirect_uris": ["<your callback URL>"],
  "client_name": "<your agent name>"
}
```

Persist `client_id`. A public client has no secret and authenticates with PKCE.

### Step 3 — Authorize

PKCE with `S256` is required. Send the user to:

```
<authorization_endpoint>?response_type=code&client_id=<client_id>&redirect_uri=<your callback URL>&code_challenge=<S256 challenge>&code_challenge_method=S256&state=<random>&resource=<resource from step 1a, percent-encoded>
```

The user signs in with their Casafari account and approves. The authorization server redirects back with `code` and `state`. If you cannot open a browser yourself, surface the URL to the user; if you cannot receive the redirect at all (no user, no callback), this flow is not for you — see [Unattended agents](#unattended-agents).

### Step 4 — Exchange the code

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

grant_type=authorization_code&code=<code>&redirect_uri=<your callback URL>&client_id=<client_id>&code_verifier=<verifier>&resource=<resource from step 1a, percent-encoded>
```

Response (200):

```json
{
  "access_token": "...",
  "token_type": "Bearer",
  "expires_in": <seconds>,
  "refresh_token": "..."
}
```

`expires_in` is authoritative for the token lifetime. Always send `resource`. A token minted without it has no audience and the MCP server rejects it.

### Step 5 — Use the access_token

Connect to `https://mcp.casafari.com/` with `Authorization: Bearer <access_token>`. `tools/list` returns only the tools the account may call; read each tool's description and input schema before calling it. Rights are re-evaluated on every request, so a revoked or extended subscription applies on your next call. Tool reference: https://docs.api.casafari.com/mcp

### Step 6 — Refresh and revocation

When the access token expires, exchange the refresh token:

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

grant_type=refresh_token&refresh_token=<refresh_token>&client_id=<client_id>&resource=<resource from step 1a, percent-encoded>
```

A `401` with `error="invalid_token"` on a previously working token means it expired or was revoked: refresh it, and if the refresh fails restart at [Step 3](#step-3--authorize). Keep the client; do not re-register.

### Unattended agents

An agent running without a user in the loop uses the client credentials grant. Such a client cannot be self-registered: Casafari issues it for the account, with a `client_id` and `client_secret`, and the account owner hands them to you. Then:

```http
POST <token_endpoint>
Content-Type: application/x-www-form-urlencoded
Authorization: Basic base64(<client_id>:<client_secret>)

grant_type=client_credentials&resource=<resource from step 1a, percent-encoded>
```

`client_secret_post` (secret in the form body) is accepted as well. Continue at [Step 5](#step-5--use-the-access_token).

## REST API

Base URL: `https://api.casafari.com`. The REST API is authenticated with a JWT obtained from the account's email and password; it does not use OAuth.

### Step 1 — Log in

```http
POST https://api.casafari.com/login
Content-Type: application/json

{ "email": "<account email>", "password": "<account password>" }
```

Response (200):

```json
{ "access_token": "<JWT>", "refresh_token": "<JWT>" }
```

### Step 2 — Use the access_token

```http
GET https://api.casafari.com/<endpoint>
Authorization: Bearer <access_token>
```

The endpoints an account may call are described in its specification: the public one at https://docs.api.casafari.com/openapi.json shows the base catalogue, and the documentation site at https://docs.api.casafari.com/ shows the account's own after signing in.

### Step 3 — Refresh

```http
GET https://api.casafari.com/refresh-token
Authorization: Bearer <refresh_token>
```

Response (200): `{ "access_token": "<JWT>" }`. A `401` from this endpoint means the refresh token expired: log in again.

## Errors

| Status | Where | Meaning | What to do |
| --- | --- | --- | --- |
| 400 `invalid_client_metadata` | registration endpoint | Unsupported `grant_types`, `redirect_uris` or auth method | Fix the request; never ask for `client_credentials` here |
| 401 `invalid_client` | token endpoint | Unknown `client_id` or wrong secret | Check the credential; a self-registered client that is gone must be registered again |
| 401 `invalid_token` | MCP server | Token missing, expired, revoked or bound to another resource | Refresh; if that fails, authorize again ([Step 3](#step-3--authorize)) |
| Tool absent from `tools/list`, or a call denied | MCP server | The account has no right to that tool | Do not retry; tell the user the subscription does not cover it |
| Tool error `Request limit reached for this product.` | MCP server | The account's quota for that product is used up | Stop calling that product; do not retry |
| 401 | REST `/login`, `/refresh-token` | Wrong credentials or expired refresh token | Ask the user for credentials, or log in again |
| 422 | REST `/login` | Malformed body | Send `email` and `password` as JSON |
| 5xx | any | Transient server error | Exponential backoff, retry the same request |