> ## Documentation Index
> Fetch the complete documentation index at: https://docs.montereyfinancial.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Authentication

> Org-scoped API keys, the Bearer header, and the 401 / 403 split.

Every Reporting API request must carry an `Authorization: Bearer <key>` header. The key resolves to a set of organization IDs the request is allowed to see; absence of authorization or a key that resolves to no orgs is rejected with `401`.

## The Bearer header

```http theme={null}
GET /reports/crr?from=2024-01-01&to=2024-12-31 HTTP/1.1
Host: api.montereyfinancial.app
Authorization: Bearer mk_your_key_here
```

The same shape works from any HTTP client:

<CodeGroup>
  ```bash cURL theme={null}
  curl -H "Authorization: Bearer mk_your_key_here" \
    "https://api.montereyfinancial.app/reports/crr?from=2024-01-01&to=2024-12-31"
  ```

  ```python Python theme={null}
  import httpx

  resp = httpx.get(
      "https://api.montereyfinancial.app/reports/crr",
      params={"from": "2024-01-01", "to": "2024-12-31"},
      headers={"Authorization": "Bearer mk_your_key_here"},
  )
  resp.raise_for_status()
  print(resp.json()["items"][:3])
  ```

  ```typescript TypeScript theme={null}
  type CrrRow = {
    transaction_id: string;
    organization_id: string;
    account_id: string;
    type: string;
    amount_cents: number;
    effective_at: string;
    status: string | null;
  };
  type CrrPage = { items: CrrRow[]; page: { next_cursor: string | null } };

  const res = await fetch(
    "https://api.montereyfinancial.app/reports/crr?from=2024-01-01&to=2024-12-31",
    { headers: { Authorization: "Bearer mk_your_key_here" } },
  );
  const data = (await res.json()) as CrrPage;
  console.log(data.items.slice(0, 3));
  ```
</CodeGroup>

## Organization scoping

When the portal mints a key, an operator picks a set of organization IDs the key is allowed to see. Two patterns are common:

* **All-of-customer**: one key covers every organization record a customer has registered with Monterey.
* **Subset**: a key sees only the orgs a particular team within the customer is responsible for.

The server enforces this on every account- and transaction-shaped query — there is no path through the API that returns a row from an organization outside the allowlist. The same scope governs the payment-operations endpoints: a key can only see and manage payment methods belonging to borrowers on in-scope accounts, and autopay schedules on in-scope accounts.

## 401 vs 403

The Reporting API uses two distinct status codes:

| Status             | When                                                       | Body                                                             |
| ------------------ | ---------------------------------------------------------- | ---------------------------------------------------------------- |
| `401 Unauthorized` | Missing / malformed / unknown / revoked / expired key      | `{"detail": {"error_code": "missing_token" \| "invalid_token"}}` |
| `403 Forbidden`    | `?organization_id=X` where X is not in the key's allowlist | `{"detail": {"error_code": "organization_out_of_scope"}}`        |

<Warning>
  An empty result is not the same as 403. If you query for a date range with no matching rows, you'll get `200` with `{"items": []}`. A 403 means the *query itself* targeted an organization you can't see.
</Warning>

Lookups by resource ID — `/accounts/{id}`, `/payment-methods/{id}`, `/autopay/{id}` — behave differently: an ID outside the key's scope returns `404`, the same as an ID that doesn't exist. The API deliberately doesn't distinguish the two, so a key can't probe for the existence of resources it isn't allowed to see.

## Rotating a key

Treat the key like a password. When you need to rotate:

1. Mint a new key in the portal — choose the same organization allowlist.
2. Update your integration to use the new key.
3. Revoke the old key in the portal. Revoked keys begin returning `401` immediately.

There is no overlap window required — the new key starts working as soon as the portal commits the row, and the old key keeps working until you revoke it.

## What we log

For every request we log the **key prefix** (the first 8 characters of the original API key, stored alongside the hash) and the resolved organization IDs. We never log the full API key, and the hash is one-way — losing your copy of the key means it cannot be recovered.
