# Sites

Source: https://docs.recoupable.dev/api-reference/sites/overview

Create, generate, and publish fan experiences through the API or MCP.

Sites uses the same backend operations from the web app, HTTP API, and MCP. A new site is a **private draft**. Generation never publishes it automatically.

## Authentication

Private HTTP endpoints accept exactly one of `x-api-key: YOUR_API_KEY` or `Authorization: Bearer YOUR_PRIVY_ACCESS_TOKEN`.
MCP uses the existing authenticated `https://api.recoupable.dev/mcp` connection.

The caller is derived from authentication. Do not send an `account_id`. An optional `organizationId` selects a workspace the caller can access. Omit it for the authenticated account's workspace.

## Endpoints and tools

| HTTP | MCP tool | Result |
| --- | --- | --- |
| `GET /api/sites` | `list_sites` | `{ sites: [...] }` |
| `POST /api/sites` | `create_site` | `{ site: ... }`, HTTP 201 |
| `GET /api/sites/{id}` | `get_site` | `{ site: ... }` |
| `PATCH /api/sites/{id}` action `concepts` | `propose_site_concepts` | Short pitches or missing context |
| `PATCH /api/sites/{id}` action `generate` | `generate_site` | Generate from an optional customer-selected pitch; otherwise select a concept |
| `PATCH /api/sites/{id}` action `generation` | `get_site_generation` | Status for the specified generation token |
| `PATCH /api/sites/{id}` action `publish` | `publish_site` | Publish the saved draft or the playable build selected by `generationToken` |
| `PATCH /api/sites/{id}` action `unpublish` | `unpublish_site` | Public snapshot removed, draft retained |
| `PATCH /api/sites/{id}` action `delete` | `delete_site` | `{ "deleted": true, "id": "..." }` |
| `GET /api/sites/{id}/signups` | `get_site_signups` | `{ signups: [{ email, created_at }] }`, up to 10,000 |
| `POST /api/sites/assets` | `upload_site_asset` | `{ asset: { url, name, type } }` |

Listing accepts optional `organizationId` and `artistId` query parameters. All private reads and writes verify workspace access. Signup emails are never part of the public response.

## Create and generate

Create a draft with a Spotify track, album, or playlist URL. Recoup retrieves its title and artwork. The brief is optional; without it, Recoup uses the available song and artist context to propose activities. Alternatively, supply both a name and a brief without a release URL.

```bash
curl https://api.recoupable.dev/api/sites \
  -H "x-api-key: $RECOUP_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"releaseUrl":"https://open.spotify.com/track/TRACK_ID","brief":"Build a maze game"}'
```

Optional creation fields: `name` (120 characters), `brief` (6,000), `organizationId`, `artistId`, and `assets` (up to eight workspace-owned assets returned by the upload endpoint). An artist must be accessible to the caller and belong to the selected organization when one is used.

Read `site.id` and `site.revision`, then start automatic generation:

```json
{"action":"generate","revision":0}
```

For Spotify tracks, the background workflow saves metadata, acquires and verifies full audio, saves Music Flamingo lyrics and musical analysis, collects artwork observations and public artist research, compiles a saved brief, selects a concept, and builds and reviews the site. No separate collector calls are required. Audio acquisition requires a Spotify preview for waveform verification and stops if no matching full recording is found. Completed accepted context is reused.

To choose a concept yourself before generation, optionally request pitches:

```json
{"action":"concepts","revision":0}
```

Send this to `PATCH /api/sites/{id}`, or call MCP `propose_site_concepts` with `id` and `revision`. Optional `instruction` and `contextBriefId` apply to pitching too. This can use credits for context collection and a pitch call, but does not create images, site code or change the draft.

The response is `{ concepts: { status, candidates, reason }, revision }`. Status is `ready`, `needs-context`, or `no-good-concept`. Metadata and cover details alone return `needs-context`; provide usable song analysis or sourced artist context rather than forcing an activity out of the artwork.

In the explicit-selection flow, show the short pitches to the customer and wait for their choice. Each candidate contains `name`, `activity`, `fanMotivation`, `songOrArtistConnection`, and `friendHook`. Pass the selected object unchanged as `approvedConcept`:

```json
{
  "action": "generate",
  "revision": 0,
  "approvedConcept": {
    "name": "CUSTOMER_SELECTED_NAME",
    "activity": "SELECTED_ACTIVITY",
    "fanMotivation": "SELECTED_MOTIVATION",
    "songOrArtistConnection": "SELECTED_CONNECTION",
    "friendHook": "SELECTED_HOOK"
  }
}
```

MCP `generate_site` accepts the same fields plus `id`, without `action`. A customer-authored concept is also accepted. The optional `approvedConcept` preserves a caller-selected activity. When omitted, the engine selects from its proposed concepts after collecting context.

Generation defaults to background execution and returns a generation token; poll `get_site_generation` (HTTP action `generation`) for the result. Set `background: false` only for a synchronous client that supports a long timeout. If review rejects the concept, the draft is marked `needs-review`; the system does not silently substitute a new activity. Asset and implementation repairs retain their bounded pass.

Generation failures identify the stopped stage and leave the saved draft unchanged. A `409` means another edit changed the revision; read the site again before retrying.

### Poll background generation

Send this JSON body to `PATCH /api/sites/{id}` using the token returned by the
generation request. MCP `get_site_generation` takes `id` and `token`, without `action`.
Polling uses `token`; publishing and cancellation use `generationToken`.

```json
{"action":"generation","token":"TOKEN_FROM_GENERATION"}
```

The token is bound to its site and caller and expires. Invalid, expired or
mismatched tokens return 404 during token verification; a missing or empty token
is invalid input (400). A running job is not proof that a
playable build or a published page exists; read the returned status and progress.

## Generate from saved Context Engine evidence

Save a single-song `creative_direction` brief using the Context Engine `save_brief` action, then pass its snapshot ID when requesting pitches:

```json
{
  "action": "concepts",
  "revision": 0,
  "contextBriefId": "SAVED_BRIEF_UUID"
}
```

Pass the same `contextBriefId` with the selected `approvedConcept` to generation. It is available on both MCP `propose_site_concepts` and `generate_site`. Without a saved brief, track generation automatically collects and saves context, reusing compatible accepted module results. The brief must belong to the site's workspace and match its Spotify track. Sites rejects unavailable or superseded snapshots before generation. Revisions reuse the draft's selected brief unless a new ID is supplied.

This path reuses accepted song summaries, artwork observations, public artist research and release/artist metadata instead of collecting them again. The creative director and generator receive attribution, coverage and missing-topic information. Supported evidence includes public Spotify metadata/artwork, YouTube-attributed audio analysis and saved public-search research. Private uploads, customer assertions and raw lyrics are excluded. Matching saved machine transcripts inform paraphrased themes but are not forwarded to the site. Partial evidence stays partial.

Generation still uses credits for creative direction, assets, implementation and review. It saves a private draft, never publishes automatically. Snapshot availability is checked again before saving and publishing. Later source withdrawal does not automatically retract an already published site. Internal evidence and source identifiers are excluded from the public site response.

## Publish

After reviewing the intended draft or preview, publish through the same PATCH
endpoint or MCP `publish_site`, using the site's current saved revision.

Publishing automatically prepares Spotify fan connection for an active paid workspace. The standard agreement uses the attributed artist's name; the customer does not need a separate setup step. Missing artist attribution or unavailable Spotify configuration stops paid publication with an actionable error. Publication requires an eligible paid workspace. The gate checks the site's owning workspace for an active subscription with a positive-priced item; a trial or Pro entitlement alone does not establish eligibility. Unpaid publication returns HTTP 402 before publishing. Successful paid setup reports `fanConnection: "enabled"`.

For a custom hosted destination, HTTP callers may include `returnUrl` (an HTTPS URL without credentials or fragment) in the publish body. Otherwise an existing connection destination is preserved, or the configured Sites public origin is used. The Recoup editor supplies its actual published URL. The authenticated fan-connection API remains available for external integrations.

The public page is `https://chat.recoupable.dev/s/{id}`.

### Publish the saved draft

Use the current revision from the saved site (3 in this example):

```json
{"action":"publish","revision":3}
```

Omitting `generationToken` selects the saved draft, even when a background job has
a newer preview. MCP `publish_site` takes `id` and `revision`, without `action`.

### Publish a working preview

To publish a specific working preview from a background generation, pass its
token as `generationToken` and the site's current saved revision:

```json
{"action":"publish","revision":3,"generationToken":"TOKEN_FROM_GENERATION"}
```

MCP `publish_site` uses the same fields plus `id`, without `action`. The token must
match the site and caller. The selected build must contain a completed playable
build step for the current site/workspace/revision; the whole generation job need
not already be completed. A preview that is not ready returns 400, a stale build
returns 409, and invalid or expired token access returns 404. A temporary saved-build
failure may return 503. Without `generationToken`, publication uses the saved draft;
it does not automatically select the newest background preview.

### Entitlement required

An ineligible workspace receives HTTP 402 with an error body, for example:

```json
{"error":"Site publishing is unavailable with this workspace's current entitlements. The draft is preserved."}
```

Keep the draft and explain that publishing is unavailable. Plugin workflows do
not offer plans, link to checkout or start a purchase. Resume only after access
is independently available and the user requests publication again. This
response does not publish the site or create a checkout session.

### Unpublish

Send `PATCH /api/sites/{id}` with the current revision to remove the public
snapshot while retaining the draft. MCP `unpublish_site` takes `id` and `revision`.

```json
{"action":"unpublish","revision":3}
```

Agents should publish, unpublish or delete only when explicitly instructed.

## Delete and cancel generation

Delete only when explicitly instructed. Send `PATCH /api/sites/{id}` with `action: "delete"` and the current `revision` in its JSON request body (Content-Type: application/json). Include `generationToken` when cancelling a known in-progress generation. The token must belong to this site and caller; cancellation is attempted before deletion. Omitting it does not request cancellation of every running job.

For example, use the current saved revision and the token returned by a known generation:

```json
{"action":"delete","revision":3,"generationToken":"TOKEN_FROM_GENERATION"}
```

Omit generationToken when no known generation needs cancellation.

Deletion checks workspace access and revision, then returns `{ "deleted": true, "id": "..." }`. A stale revision returns 409; reload before retrying. Unpublish with `action: "unpublish"` when the draft should remain available.

## Uploads

HTTP uploads are multipart forms with a `file` field and optional `organizationId` query parameter. Supported formats: JPEG, PNG, WebP, MP3, and WAV, up to 4 MB per file. Images are re-encoded to WebP. Audio is checked for its file signature.

MCP's `upload_site_asset` accepts `name`, `contentType`, `base64` file bytes, and optional `organizationId`. It uses the same validation and storage path as HTTP.

## Public endpoints

- `GET /api/sites/public/{id}` returns `{ snapshot: ... }` for a published site only. Drafts, account identifiers, and signup data are excluded.
- `POST /api/sites/public/{id}/signup` accepts `{"email":"fan@example.com","consent":"yes"}`. An optional `website` honeypot must be empty. Duplicate submissions succeed without disclosing list membership. Unpublished sites reject signups.

Public pages and the browser music player render in the chat app. The API provides
snapshots, generation, uploads, signup persistence and the fan-connection integration
routes listed below.

## Route coverage

The lifecycle and public snapshot/signup operations above are documented here in
prose; they are not a complete Sites OpenAPI specification. The following existing
routes are accounted for as integration surfaces, outside the copyable lifecycle
examples. Listing them does not establish live authorization or add an MCP tool.

### Authenticated integration routes

| HTTP | Purpose |
| --- | --- |
| `GET /api/sites/{id}/fan-connection` | Read fan-connection configuration |
| `PUT /api/sites/{id}/fan-connection` | Configure the fan connection through the authenticated integration |
| `GET /api/sites/{id}/fans` | Fan reporting |
| `GET /api/sites/{id}/activity` | Activity reporting |

### Browser integration routes

| HTTP | Purpose |
| --- | --- |
| `GET /api/sites/apple/config` | Browser music configuration |
| `POST /api/sites/public/{id}/activity` | Published-site activity collection |
| `GET /api/sites/public/{id}/spotify` | Show the fan-connection acceptance page |
| `POST /api/sites/public/{id}/spotify` | Begin Spotify authorization after acceptance |

These browser flows are not replacements for a private lifecycle request or proof
of a completed fan connection. Email signup and Spotify connection are distinct.

### Provider callback

`GET /api/sites/spotify/callback` completes the provider redirect flow. It is
intentionally excluded from agent lifecycle examples; do not invoke it as a
standalone operation. OPTIONS preflight handlers are also excluded from the
operation tables because they serve browser CORS negotiation.

## Errors

- `402`: publication requires an eligible paid workspace.

`400` invalid input; `401` missing/invalid authentication; `403` workspace or artist access denied; `404` missing/unpublished site; `409` stale revision; `422` Spotify metadata unavailable; `503` temporary operation failure.
