-
Notifications
You must be signed in to change notification settings - Fork 2
docs: add authentication page covering API keys, access tokens, and o… #63
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -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 <token>` | 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** | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| <Warning> | ||||||||||||||||||||||||||||||||
| 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. | ||||||||||||||||||||||||||||||||
| </Warning> | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| ### 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 | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| <Info> | ||||||||||||||||||||||||||||||||
| An account is recognized as an organization when it has at least one member in the `account_organization_ids` table. | ||||||||||||||||||||||||||||||||
| </Info> | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| --- | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| ## 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. | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
|
Comment on lines
+79
to
+90
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Section title contradicts behavior timing. Line 79 says key type is determined “at Creation,” but Line 89 says the check happens at authentication time. Please align the title and copy so the behavior is unambiguous. Suggested wording update-## How We Determine Key Type at Creation
+## How Key Type Is Resolved
@@
-When a key is created under an account, the API checks whether that account has any organization members:
+When a key is used, the API checks whether the owning account currently has any organization members:📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||
| --- | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| ## 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. | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| <Note> | ||||||||||||||||||||||||||||||||
| The Recoup internal admin organization has universal access to all accounts. | ||||||||||||||||||||||||||||||||
| </Note> | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| ### 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) | ||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -19,6 +19,8 @@ | |
| "index", | ||
| "quickstart", | ||
| "mcp", | ||
| "sdk", | ||
| "authentication", | ||
| "cli", | ||
| "api-reference/sandboxes/create" | ||
| ] | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.