diff --git a/authentication.mdx b/authentication.mdx new file mode 100644 index 0000000..427727b --- /dev/null +++ b/authentication.mdx @@ -0,0 +1,140 @@ +--- +title: "Authentication" +description: "How authentication works in the Recoup API — API keys, access tokens, and organization access control." +--- + +## Overview + +Every request to the Recoup API must be authenticated using exactly one of two mechanisms: + +| Method | Header | Use case | +|--------|--------|----------| +| API Key | `x-api-key` | Server-to-server integrations | +| Access Token | `Authorization: Bearer ` | Frontend apps authenticated via Privy | + +Providing both headers in the same request will result in a `401` error. + +--- + +## API Keys + +API keys are the primary way to authenticate programmatic access to the Recoup API. + +### Creating an API Key + +1. Navigate to [chat.recoupable.com/keys](https://chat.recoupable.com/keys) +2. Enter a descriptive name (e.g. `"Production Server"`) +3. Click **Create API Key** + + +Copy your API key immediately — it is only shown once. Keys are stored as a secure HMAC-SHA256 hash and cannot be retrieved after creation. + + +### Using an API Key + +Pass your key in the `x-api-key` header: + +```bash +curl -X GET "https://api.recoupable.com/api/tasks" \ + -H "x-api-key: YOUR_API_KEY" +``` + +--- + +## Access Tokens (Privy) + +If you're building a frontend application that authenticates users via [Privy](https://privy.io), you can pass the user's Privy JWT as a Bearer token instead of an API key. + +```bash +curl -X GET "https://api.recoupable.com/api/tasks" \ + -H "Authorization: Bearer YOUR_PRIVY_JWT" +``` + +The API validates the token against Privy, extracts the user's email, and resolves it to the corresponding Recoup account. Bearer tokens always authenticate as a personal account — they cannot act on behalf of an organization. + +--- + +## Personal vs. Organization API Keys + +API keys inherit the type of account they were created under. + +### Personal API Keys + +- Created by a standard user account +- Can only access **your own account's data** +- `orgId` is `null` on the resolved auth context + +### Organization API Keys + +- Created by an organization account (an account that has members) +- Can access data for **any member account** within the organization +- `orgId` is set to the organization's account ID + + +An account is recognized as an organization when it has at least one member in the `account_organization_ids` table. + + +--- + +## How We Determine Key Type at Creation + +When a key is created under an account, the API checks whether that account has any organization members: + +``` +Has members in account_organization_ids? + ├── Yes → Organization API Key (orgId = accountId) + └── No → Personal API Key (orgId = null) +``` + +This check happens at **authentication time** (not creation time), so key behavior automatically reflects the current state of the account. + +--- + +## How We Verify Access on API Calls + +Every authenticated request goes through `validateAuthContext`, which enforces the following access rules: + +### Personal API Key or Bearer Token + +Can only access their **own account**. Attempting to pass an `account_id` belonging to another account returns `403 Forbidden`. + +### Organization API Key + +Can access **any account that is a member of the organization**: + +``` +Request includes account_id override? + ├── Same as key owner → Allowed (self-access) + ├── Is a member of the org → Allowed + └── Not a member → 403 Forbidden +``` + +Membership is verified by querying the `account_organization_ids` table for a record linking the target account to the organization. + + +The Recoup internal admin organization has universal access to all accounts. + + +### Organization Access via `organization_id` + +Some endpoints accept an `organization_id` parameter. When provided, the API additionally validates that the authenticated account is either: + +- A **member** of the organization, or +- The **organization account itself** + +--- + +## Error Responses + +| Status | Cause | +|--------|-------| +| `401` | Missing or invalid credentials, or both `x-api-key` and `Authorization` headers provided | +| `403` | Valid credentials but insufficient access to the requested `account_id` or `organization_id` | + +--- + +## Security Notes + +- API keys are **never stored in plaintext** — only an HMAC-SHA256 hash (keyed with your project secret) is persisted in the database +- **Never include `account_id` in your API key creation request** — the account is always derived from your authenticated credentials +- Rotate keys immediately if compromised via the [API Keys Management Page](https://chat.recoupable.com/keys) diff --git a/docs.json b/docs.json index 3aa26de..7f818b8 100644 --- a/docs.json +++ b/docs.json @@ -19,6 +19,8 @@ "index", "quickstart", "mcp", + "sdk", + "authentication", "cli", "api-reference/sandboxes/create" ]