Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
198 changes: 137 additions & 61 deletions agents.mdx
Original file line number Diff line number Diff line change
@@ -1,96 +1,172 @@
---
Comment thread
cubic-dev-ai[bot] marked this conversation as resolved.
title: 'Agents'
description: 'Programmatic agent onboarding — sign up and obtain API keys in one call, no dashboard, no human in the loop.'
title: "Agent Onboarding"
description: "The operating manual for AI agents using the Recoup API."
---

## Quickest start
This page is for AI agents that act on a user's behalf. Follow the steps in order.

Get a working API key in a single unauthenticated request:
<Note>
**Just trying the API yourself?** See [Quickstart](/quickstart) for a 5-minute hello-world. This page is the production operating manual — it covers the throwaway-vs-account decision, the roster, the filesystem, and the agent loop.
</Note>

```bash
curl -X POST "https://recoup-api.vercel.app/api/agents/signup" \
-H "Content-Type: application/json" \
-d '{"email": "agent+'$(date +%s)-$RANDOM'@recoupable.com"}'
```
## 0. Choose your path

Response:
**Ask the human which path they want before authenticating.** Guessing wrong creates records under a throwaway account that can't be recovered if the API key is lost — the user re-prompts later and their roster is gone.

```json
{
"account_id": "123e4567-e89b-12d3-a456-426614174000",
"api_key": "recoup_sk_abc123...",
"message": "If this is a new agent+ email, your API key is included. Otherwise, check your email for a verification code."
}
```
Use this question — paraphrase if needed, but keep the trade-off explicit:

That's it. Store `api_key`, pass it in the `x-api-key` header on every subsequent request, and you're done.
> "Are you just exploring Recoup, or do you want me to set this up against your account so the work persists? If you have an account, what email should I use? Otherwise I'll create a temporary account — but you won't be able to save artists, releases, or scheduled tasks under it."

<Tip>
**One-liner — sign up and export the key in one shot.** Drop this into your shell and you'll have `$RECOUP_API_KEY` ready to use on the next line:
Once you have the answer, match the human's intent to the right path:

```bash
export RECOUP_API_KEY=$(curl -s -X POST "https://recoup-api.vercel.app/api/agents/signup" \
-H "Content-Type: application/json" \
-d '{"email": "agent+'$(date +%s)-$RANDOM'@recoupable.com"}' | jq -r .api_key)
```
| Human's answer | Auth path | Then |
|----------------|-----------|------|
| "Just exploring / quick demo / one-shot research" | Throwaway [`agent+...@recoupable.com`](#1-authenticate) signup | Use research and content endpoints. **Don't create artists** — they'll orphan when the API key is lost. |
| "I have an account, here's my email" — they want to work on their existing roster | Sign in with their real email | List [`/api/artists`](/api-reference/artists/list); work with the roster they already have |
| "I have an account, here's my email" — they want to add a new artist | Sign in with their real email | Run the [Create a New Artist](/workflows/create-artist) workflow |
| "I don't have an account but I want to set one up" | Sign in with their real email | After auth, run [Create a New Artist](/workflows/create-artist) to start their roster |

Verify it worked:
**The key distinction:** `agent+` emails create isolated, unrecoverable accounts. Use them ONLY for one-shot work (research, content generation) where losing the API key has no cost. For anything that creates persistent records — artists, releases, scheduled tasks — use the user's real email.

```bash
curl -H "x-api-key: $RECOUP_API_KEY" https://recoup-api.vercel.app/api/accounts/id
```
</Tip>
**When you can skip the question:** If the human's instruction explicitly names the path (e.g., *"just try the demo"*, *"use my Recoup account, my email is x@y.com"*, or you were invoked autonomously by another agent with no human in the loop), you don't need to ask — proceed directly.

<Tip>
The `agent+{unique-suffix}@recoupable.com` shape is the recommended path for agents — it always returns an API key instantly, with no email verification required. Combining `$(date +%s)` with `$RANDOM` guarantees a fresh, collision-free address on every call (including multiple signups within the same second) and is portable across macOS and Linux shells.
</Tip>
## 1. Authenticate

## How it works
If your human has an API key, pass it via `x-api-key`. If not, generate one based on the path you chose above:

Two unauthenticated endpoints power agent onboarding:
<Steps>
<Step title="Call signup with their email">
```bash
curl -X POST "https://recoup-api.vercel.app/api/agents/signup" \
-H "Content-Type: application/json" \
-d '{"email": "user@example.com"}'
```
</Step>
<Step title="Ask the human for the code">
A 6-digit code was sent to their inbox. Ask them: *"Check your email for a verification code and share it with me."*
</Step>
<Step title="Call verify with the code">
```bash
curl -X POST "https://recoup-api.vercel.app/api/agents/verify" \
-H "Content-Type: application/json" \
-d '{"email": "user@example.com", "code": "123456"}'
```
Store the returned `api_key` (e.g. `export RECOUP_API_KEY=your-api-key`) and pass it as `x-api-key` on every request.
</Step>
</Steps>

- **[`POST /api/agents/signup`](/api-reference/agents/signup)** — Register with an email address. Emails with the `agent+` prefix that have never been seen before receive an API key immediately. Any other email (or a previously-used `agent+` address) receives a 6-digit verification code via email.
- **[`POST /api/agents/verify`](/api-reference/agents/verify)** — Submit the verification code to receive an API key.
**After authenticating, immediately check the roster.** Don't wait for the human to tell you what to do.

Multiple API keys per account are supported — each signup or verification generates a new key without revoking existing ones.
<Warning>
`agent+` emails create a **separate account** — `agent+user@example.com` is NOT linked to `user@example.com`. To work on behalf of an existing human, use their real email.
</Warning>

## Standard signup (email verification)
---

If you're building a human-facing integration and want the user to verify their real email, use any non-`agent+` address:
## 2. Understand the roster

Step 1 — request a verification code:
After getting a key, your next call should always be to check what the human has:

```bash
curl -X POST "https://recoup-api.vercel.app/api/agents/signup" \
-H "Content-Type: application/json" \
-d '{"email": "you@example.com"}'
# List all artists available to this account
curl "https://recoup-api.vercel.app/api/artists" \
-H "x-api-key: $RECOUP_API_KEY"

# List organizations (labels/teams) the account belongs to
curl "https://recoup-api.vercel.app/api/organizations" \
-H "x-api-key: $RECOUP_API_KEY"
```

**If the human has artists**, you can scope work to a specific artist by passing `artist_account_id` on supported endpoints. Research, content, tasks, and fan data all become artist-specific.

**If the human has organizations**, pass `organization_id` to scope to a specific label's roster.

**If neither is specified**, you operate at the account level and can see everything available to the human.

---

## 3. Know the filesystem

Each account has a persistent filesystem backed by a GitHub repo. This is where artist context lives — the files agents use to do informed work.

### Artist directory structure

```
orgs/{org-name}/artists/{artist-slug}/
├── RECOUP.md # Identity file (artistName, artistSlug, artistId)
├── context/
│ ├── artist.md # Brand voice, bio, constraints
│ ├── audience.md # Audience insights, resonance
│ ├── era.json # Current era metadata
│ └── images/
│ └── face-guide.png # Face reference for visual content
├── songs/{song-slug}/
│ └── {song-slug}.mp3 # Audio files
├── releases/{release-slug}/
│ └── RELEASE.md # Release plan and metadata
└── config/
└── content-creation/
└── config.json # Pipeline overrides
```

The `RECOUP.md` file ties the folder to the platform — it contains YAML frontmatter with `artistName`, `artistSlug`, and `artistId`.
Comment on lines +75 to +112
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Fix the filesystem path contradiction.

This section says there is no orgs/ directory and that artists live at the top level, but the example tree still uses orgs/{org-name}/artists/{artist-slug}. That inconsistency will send agents to the wrong path.

Suggested fix
-orgs/{org-name}/artists/{artist-slug}/
+artists/{artist-slug}/
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@agents.mdx` around lines 63 - 100, The filesystem section contradicts itself
about where artist folders live; update either the prose or the example tree so
they match: if artists live at the top level, change the example path from
orgs/{org-name}/artists/{artist-slug}/ to artists/{artist-slug}/ (keeping
RECOUP.md, context/, songs/, releases/, and config/ entries and filenames like
RECOUP.md and config/content-creation/config.json), otherwise change the prose
to say artists live under orgs/{org-name}/artists/{artist-slug}/ so the text and
the example tree are consistent.


Step 2 — submit the 6-digit code from the verification email:
### Accessing sandbox files

```bash
curl -X POST "https://recoup-api.vercel.app/api/agents/verify" \
# List the full file tree
curl "https://recoup-api.vercel.app/api/sandboxes" \
-H "x-api-key: $RECOUP_API_KEY"

# Read a specific file
curl "https://recoup-api.vercel.app/api/sandboxes/file?path=orgs/my-label/artists/drake/context/artist.md" \
-H "x-api-key: $RECOUP_API_KEY"

# Upload files to the repo
# path is top-level (target directory); each file needs url + name
curl -X POST "https://recoup-api.vercel.app/api/sandboxes/files" \
-H "x-api-key: $RECOUP_API_KEY" \
-H "Content-Type: application/json" \
-d '{"email": "you@example.com", "code": "123456"}'
-d '{"path": "orgs/my-label/artists/drake/context", "files": [{"url": "https://...", "name": "audience.md"}]}'
```

Response:
---

```json
{
"account_id": "123e4567-e89b-12d3-a456-426614174000",
"api_key": "recoup_sk_abc123...",
"message": "Verified"
}
```
## 4. Decide what to do

## Using your API key
<Steps>
<Step title="Does the human have a roster?">
Call `GET /api/artists`. If they have artists, list them and ask which one to work with. If not, you can research any artist with `GET /api/research?q=...` or create one with `POST /api/artists`.
</Step>
<Step title="What kind of work?">
**Research** — use the 30+ research endpoints. Pass `artist_account_id` to scope to a rostered artist, or search by name for any artist globally.

Pass the returned `api_key` in the `x-api-key` header on every authenticated request:
**Content** — generate images, videos, and captions with the content endpoints. Artist context from the filesystem makes output more on-brand.

```bash
curl -X GET "https://recoup-api.vercel.app/api/tasks" \
-H "x-api-key: YOUR_API_KEY"
**Manage** — plan and track releases by creating and updating `RELEASE.md` files in the artist's `releases/` directory. Add songs to catalogs, update artist context, and organize the roster.
</Step>
<Step title="Should you save the work?">
Save research, generated content, or notes to the artist's directory in the filesystem so future calls have more context. Use `POST /api/sandboxes/files` to upload files to the repo.
</Step>
<Step title="Should this be recurring?">
If the human asks for something more than once, or if the work is time-sensitive and repeating, turn it into a task with `POST /api/tasks`.

Good candidates for tasks:
- Daily or weekly reports (streaming stats, fan growth, playlist adds)
- Monitoring competitors or trending artists
- Generating recurring content (weekly social posts, monthly recaps)
- Checking release milestones as a date approaches

If the human only needs it once, just do it. Don't create a task for everything.
</Step>
</Steps>

---

## Base URL

```
https://recoup-api.vercel.app/api
```

See [Authentication](/authentication) for the full authentication model, including organization access and Bearer token support, and [Quickstart](/quickstart) for your first end-to-end request.
All endpoints require `x-api-key` header unless noted. See [Authentication](/authentication) for the full auth model, and the [endpoint map](/#for-ai-agents) for every available endpoint.
111 changes: 45 additions & 66 deletions authentication.mdx
Original file line number Diff line number Diff line change
@@ -1,115 +1,94 @@
---
title: "Authentication"
description: "How authentication works in the Recoup API — API keys, access tokens, and organization access control."
description: "API keys and Bearer tokens — how to authenticate every request to the Recoup API."
---

## Overview
**Use API keys** for server-to-server, CLI, and agent integrations. **Use Bearer tokens** for frontend apps authenticated via Privy. Include exactly one — providing both returns `401`.

Every request to the Recoup API must be authenticated using exactly one of two mechanisms:

| Method | Header | Use case |
| Method | Header | Best for |
|--------|--------|----------|
| 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 Key | `x-api-key` | Servers, scripts, CLI, AI agents |
| Bearer Token | `Authorization: Bearer <jwt>` | Frontend apps via Privy |

<Note>
Agent onboarding endpoints (`POST /api/agents/signup` and `POST /api/agents/verify`) are **unauthenticated** — they exist so agents can obtain their first API key. See the [Agents guide](/agents) for details.
The [agent signup and verify](/agents) endpoints (`POST /api/agents/signup` and `POST /api/agents/verify`) are both unauthenticated — they let agents get their first key without any credentials.
</Note>

---

## API Keys

API keys are the primary way to authenticate programmatic access to the Recoup API. All API keys are **personal keys** — they are always tied to the account that created them.
## Create a key

### Creating an API Key
Two ways to get a 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**
- **Via API** — see [Quickstart](/quickstart#1-get-your-api-key) for the two-call signup + verify flow. When an agent runs this on behalf of a human, the agent passes the code the human reads back from their inbox.
- **Via dashboard** — go to [chat.recoupable.com/keys](https://chat.recoupable.com/keys), sign in, and create one.

<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.
Keys are shown once. They are stored as HMAC-SHA256 hashes and cannot be retrieved after creation.
</Warning>

### Using an API Key
---

Pass your key in the `x-api-key` header:
## Use a key

```bash
curl -X GET "https://recoup-api.vercel.app/api/tasks" \
curl "https://recoup-api.vercel.app/api/research?q=Drake" \
-H "x-api-key: YOUR_API_KEY"
```

### Access to Organizations
---

If your account belongs to one or more organizations, your API key can access data across those organizations by passing an `account_id` parameter on supported endpoints. This lets you filter to any account within an organization your key has access to.
## Organization access

- **No org membership** — the key can only access its own account's data
- **Org member** — the key can pass `account_id` to filter to any account within that organization
If your account belongs to an organization, your key can access data for any account in that org by passing `account_id`:

<Info>
Org membership is determined by the account's [organizations](/api-reference/organizations/list). An account gains access to an org when it is added as a member.
</Info>
- **No org** — key accesses its own data only
- **Org member** — key can pass `account_id` to access any member's data

---

## Access Tokens (Privy)
## Bearer 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.
For frontend apps with [Privy](https://privy.io) authentication:

```bash
curl -X GET "https://recoup-api.vercel.app/api/tasks" \
curl "https://recoup-api.vercel.app/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.
The API validates the JWT against Privy, extracts the user's email, and resolves it to a Recoup account.

---

## How We Verify Access on API Calls
## Access control

Every authenticated request goes through `validateAuthContext`, which enforces the following access rules:

### API Key or Bearer Token

By default, requests access the key owner's own account. When `account_id` is provided:
Scoping parameters follow the same organization-membership rule:

```
Request includes account_id override?
├── Same as key owner → Allowed (self-access)
├── Key owner is a member of an org that contains account_id → Allowed
└── No matching org membership → 403 Forbidden
Request includes account_id?
├── Same as key owner → allowed
├── Shares an organization → allowed
└── No shared org → 403

Request includes organization_id?
├── Key owner is a member of that org → allowed
└── Not a member → 403

Request includes artist_account_id?
├── Artist belongs to the key owner → allowed
├── Artist belongs to an org the key owner is a member of → allowed
└── Neither → 403
```

Membership is verified by checking the key owner's [organizations](/api-reference/organizations/list) for a record linking the account to the target account's 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
## Errors

| 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` |

---
| `401` | Missing/invalid credentials, or both headers |
| `403` | Valid credentials, insufficient access |

## Security Notes
## Security

- 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)
- Keys stored as HMAC-SHA256 hashes — never plaintext
- Rotate compromised keys at [chat.recoupable.com/keys](https://chat.recoupable.com/keys)
- Never commit keys to version control
Loading