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

# Authentication

> How to authenticate requests to the Grand Charter public API with an integration API key.

Every request to the public API is authenticated with an **integration API key**, sent in the `x-api-key` header:

```bash theme={null}
curl https://www.presolve.com/api/v1/cases?view=full \
  -H "x-api-key: YOUR_API_KEY"
```

A missing or invalid key returns `401`:

```json theme={null}
{
  "success": false,
  "error": "Invalid or expired integration API key",
  "code": "UNAUTHORIZED"
}
```

## Creating a key

Members with Manage Integrations create integration keys in the attorney portal under **Settings → Integrations → API Credentials** (requires the integrations-management permission). At creation you choose:

* a **name** (surfaced as the actor name in events and audit logs),
* the **scopes** the key holds (see [Scopes](/docs/scopes) — standard scopes only; elevated PHI/PII scopes are admin-granted),
* an **acting attorney** — required when the selected scopes need one (see the [scope ↔ acting-attorney matrix](/docs/scopes#scope--acting-attorney-matrix)),
* an **expiry** (`validUntil` is required; maximum 2 years, or 1 year for keys holding elevated scopes),
* for reporting, **Current organization + child organizations** — on by default; HQ rollups require the acting attorney’s current HQ reporting and data permissions,
* optionally, **webhook allowed hosts** — the HTTPS host allowlist for webhook subscription URLs.

The plaintext secret is shown **exactly once**, at creation. Only a SHA-256 digest and a short prefix are stored; the prefix is displayed in the UI and in `GET /me` so you can tell keys apart.

Keys can also be managed programmatically with the `org:manage` scope via the `/service-accounts` endpoints — see [Service accounts](/docs/service-accounts).

## Key lifecycle

* **Multiple active keys per org.** Keys are additive and independently revocable, which is what makes zero-downtime rotation possible: create a new key, migrate your integration, then revoke the old one.
* **Reconfiguration without rotation.** Name, standard scopes, acting attorney, report hierarchy preference, and webhook hosts can be changed without touching the secret.
* **Revocation** takes effect immediately; revoked and expired keys fail closed.
* **Expiry** is mandatory — plan rotation before `expiresAt` (visible in `GET /me`).

## Scope enforcement

Most endpoints declare a single required scope (shown in the API Reference as `x-required-scope`). The exceptions: `GET /me` requires no scope at all (it is the credential health check), and a few endpoints accept a legacy alternative for backwards compatibility (e.g. `GET /cases` honors `documents:read` where `cases:read` is the primary scope). A key without the required scope receives `403`:

```json theme={null}
{
  "success": false,
  "error": "API key lacks required permissions",
  "code": "FORBIDDEN"
}
```

Scopes are explicit grants — `write` never implies `read`, and there are no wildcard scopes.

## Acting attorney

Some scopes require the key to be configured with an **acting attorney** (an active member of your organization). Writes made by such a key are attributed to the service account *acting on behalf of* that attorney — this is how document ownership, note/task/e-sign authorship, and compliance attribution work. If the acting attorney's membership is deactivated, affected requests fail with `INTEGRATION_NOT_CONFIGURED` until the key is reconfigured.

The acting attorney is not a substitute for API scopes. For HQ reporting, current organization roles determine which descendant data the key may read. For other endpoints, their existing key scopes and organization boundaries still apply. See [Organizations, HQ, and permissions](/docs/authorization).

## Tenancy

Queries, mutations, and event reads normally stay within the key's organization. Saved report runs (`POST /api/v1/reports/{reportId}/run`) can also include authorized descendants of an HQ organization. The **Current organization + child organizations** credential toggle (`reportIncludeChildOrganizations`) defaults to on for existing and new keys. Hierarchy reporting requires an acting attorney with current HQ visibility, report viewing/export, and the report's data permissions. The toggle does not grant permissions. Default HQ rollups return `403` if the acting attorney is missing or no longer authorized, rather than silently shrinking to HQ-only data. Turn it off to restrict report runs to the key's own organization.

For report runs, pass `?organizationIds=<org-id>,<child-id>` to select a narrower authorized scope. Unauthorized IDs return `403`; shared child reports must also be API-visible, otherwise `404`. Incompatible field definitions return `400`. The report owner's fields and saved filters still apply. Report listing, metadata lookup, and export jobs remain scoped to the key's own organization.

## Legacy partner credentials

Two endpoint families predate integration keys and continue to accept **partner API keys** and **OIDC bearer tokens** (`Authorization: Bearer <token>`) unchanged: case create/update (`POST /cases`, `PATCH /cases/{caseId}`) and lead create/update (`POST /leads`, `PATCH /leads/{leadId}`). Integration keys work on those endpoints too, with scope enforcement (`cases:write` / `leads:write`). All other `/api/v1` endpoints require an integration key; bearer tokens are rejected there today.

> **Planned:** an OAuth 2.0 client-credentials flow issuing short-lived bearer tokens, using the same scope vocabulary. It has not shipped; the `x-api-key` header is the supported authentication method.

## Request checklist

1. Send `x-api-key` on every request.
2. Send `X-Idempotency-Key` on mutations (any stable unique string up to 200 chars, e.g. a UUID per logical operation). Each operation's API Reference entry says whether it consumes the header. **Exceptions that ignore it — do not blind-retry these**: `POST /leads`, `PATCH /leads/{leadId}`, and `PATCH /cases/{caseId}`; retrying a timed-out call there can create duplicate records, so confirm outcome with a read first.
3. Send `If-Match: "<revision>"` when updating revisioned entities (documents, folders), using the last `ETag` you saw.
4. Watch `X-RateLimit-Remaining` and back off on `429` per `Retry-After`.
