# Create a new artist

Source: https://docs.recoupable.dev/workflows/create-artist

End-to-end workflow to create, research and enrich a new artist account in a single session.

This is the canonical recipe used internally by Recoup's chat agent. Follow it step-by-step to bring a new artist account up to "researched + enriched" parity from a sandbox or any external agent.

The chain is 8 sequential steps. Long deterministic chains executed from prose memory tend to drop steps — the agent reads the doc once, runs a couple of calls, and forgets the rest. To prevent that from a sandbox, **drive the work from a checklist file**: scaffold the artist's `RECOUP.md` with one checkbox per step before any API call, then tick each box and persist captured values back to the frontmatter as you go. The file becomes the workflow state, and a fresh turn can resume by reading it.

## Prerequisites

- `$RECOUP_API_KEY` (`x-api-key`) or `$RECOUP_ACCESS_TOKEN` (Privy Bearer JWT) for `api.recoupable.dev`
- `$RECOUP_ORG_ID` — the org the artist should belong to (recommended in sandboxes)
- An artist name to create (e.g. `ARTIST_NAME="The Weeknd"`)

Select one credential obtained through the approved real-account REST setup:

```bash
if [ -n "$RECOUP_API_KEY" ]; then
  AUTH=(-H "x-api-key: $RECOUP_API_KEY")
elif [ -n "$RECOUP_ACCESS_TOKEN" ]; then
  AUTH=(-H "Authorization: Bearer $RECOUP_ACCESS_TOKEN")
else
  echo "Authenticate REST through the approved real-account setup flow." >&2
  exit 1
fi
```

Use your real account and selected organization. Do not create a disposable
`agent+` identity or paste credentials into the workspace file.

The flow has three phases, run from a single checklist file:

1. **Create + identify** — `POST /api/artists`, then find the canonical Spotify match
2. **Enrich** — `PATCH` the artist with image/label/socials, then read available Recoup research and search the web for narrative context and additional socials
3. **Synthesize + persist** — generate a knowledge-base report, save it (RECOUP.md tree or hosted URL), then optionally `PATCH` the `knowledges` array

## Step 0: Scaffold the workspace BEFORE any API call

Pick a slug, make the directory, and write the initial `RECOUP.md` template — frontmatter holds the values the chain captures (filled as you go); body holds the unchecked steps:

```bash
ARTIST_SLUG=$(echo "$ARTIST_NAME" | tr '[:upper:]' '[:lower:]' | sed 's/[^a-z0-9]\+/-/g; s/^-//; s/-$//')
ARTIST_DIR="artists/$ARTIST_SLUG"
mkdir -p "$ARTIST_DIR"

cat > "$ARTIST_DIR/RECOUP.md" <<EOF
---
artistName: $ARTIST_NAME
artistSlug: $ARTIST_SLUG
artistId:
spotifyArtistId:
spotifyProfileUrl:
imageUrl:
researchArtistId:
---

# $ARTIST_NAME

## Setup checklist

- [ ] 1. Create the artist (\`POST /api/artists\`) — capture \`account_id\` → \`artistId\`
- [ ] 2. Find canonical Spotify match (\`GET /api/spotify/search\`) — capture \`id\`, \`external_urls.spotify\`, \`images[0].url\`
- [ ] 3. PATCH artist with image + Spotify profile URL
- [ ] 4. Recoup research — resolve \`researchArtistId\`, read available profile/activity/playlists, then \`POST /research/web\`. Save responses and coverage gaps under \`## Research\`.
- [ ] 5. Pull Spotify catalog → write \`releases/{album-slug}/RELEASE.md\` per album + \`releases/top-tracks.md\`
- [ ] 6. Web search for additional socials (instagram / tiktok / twitter / youtube)
- [ ] 7. PATCH artist with discovered socials
- [ ] 8. Synthesize knowledge base — append it as \`## Knowledge base\` in this file

## Notes
EOF
```

The sandbox is already scoped to a single Recoupable organization (its repo *is* the org), so artists live at the top level — there is no `orgs/` directory. Don't proceed to step 1 until the file exists on disk.

**After every step that follows, do two writes back to `RECOUP.md`:** tick the checkbox for the step you just ran (`- [ ]` → `- [x]`) and persist any captured value into the frontmatter. Later steps read those values from the frontmatter — never re-derive what's already saved.

## Resuming a partial setup

If `$ARTIST_DIR/RECOUP.md` already exists, do not re-scaffold and do not re-run completed steps. Read the file, find the first unchecked item, and resume from there using the values already saved in the frontmatter:

```bash
# Show the next unchecked step
grep -n '^- \[ \]' "$ARTIST_DIR/RECOUP.md" | head -1
```

If every item is checked, the artist is fully set up — confirm with the user before doing anything else.

## Step 1: Create the artist

```bash
ARTIST_RESPONSE=$(curl -sS -X POST "https://api.recoupable.dev/api/artists" \
  "${AUTH[@]}" \
  -H "Content-Type: application/json" \
  -d "$(jq -n --arg name "$ARTIST_NAME" --arg org "$RECOUP_ORG_ID" \
        '{name: $name, organization_id: $org}')")

ARTIST_ID=$(echo "$ARTIST_RESPONSE" | jq -r '.artist.account_id')
```

Capture `account_id` as `$ARTIST_ID` — every subsequent step needs it. `organization_id` is optional but should be included when running inside a sandbox so the artist is scoped to the right org.

See [Create Artist](https://docs.recoupable.dev/api-reference/artists/create) for the full request/response schema.

**After this step:** write `artistId: $ARTIST_ID` into the frontmatter and tick `- [ ] 1.` → `- [x] 1.` in `RECOUP.md`.

## Step 2: Find the canonical Spotify match

```bash
SPOTIFY=$(curl -sS -G "https://api.recoupable.dev/api/spotify/search" \
  "${AUTH[@]}" \
  --data-urlencode "q=$ARTIST_NAME" \
  --data-urlencode "type=artist" \
  --data-urlencode "limit=10")
```

Inspect the returned candidates' names, profile links, images and catalog.
Set `SELECTED_SPOTIFY_ARTIST_ID` to the verified match; popularity alone does not
establish identity. If candidates remain ambiguous, resolve that before saving
the artist's profile.

```bash
printf '%s' "$SPOTIFY" | jq '.artists.items[]? | {id, name, external_urls, images}'
```

If the verified artist is absent from the first ten results, repeat the search
with a more specific name or known catalog context and inspect the new results.
Do not substitute another artist. After verifying the candidate, export its fields:

```bash
: "${SELECTED_SPOTIFY_ARTIST_ID:?Set the verified Spotify artist ID first}"
MATCH=$(printf '%s' "$SPOTIFY" | jq --arg id "$SELECTED_SPOTIFY_ARTIST_ID" \
  '.artists.items[]? | select(.id == $id)')

SPOTIFY_ARTIST_ID=$(printf '%s' "$MATCH" | jq -r '.id // empty')
SPOTIFY_PROFILE_URL=$(printf '%s' "$MATCH" | jq -r '.external_urls.spotify // empty')
SPOTIFY_IMAGE_URL=$(printf '%s' "$MATCH" | jq -r '.images[0].url // empty')

[ -n "$SPOTIFY_ARTIST_ID" ] || { echo "No verified Spotify match for $ARTIST_NAME"; exit 1; }
```

Also keep `genres`, `followers.total`, and `popularity` from `$MATCH` for the KB report later.

See [Spotify Search](https://docs.recoupable.dev/api-reference/spotify/search) for the full query parameters and response schema.

**After this step:** write `spotifyArtistId`, `spotifyProfileUrl`, and `imageUrl` into the frontmatter, save `genres` / `followers.total` / `popularity` to the `## Notes` section, and tick `- [ ] 2.` → `- [x] 2.`.

## Step 3: Set basic profile + Spotify URL

One `PATCH` covers the image and the Spotify social URL. Use **uppercase** platform keys in `profileUrls` (the API matches platforms case-sensitively — see the reference table below).

```bash
curl -sS -X PATCH "https://api.recoupable.dev/api/artists/$ARTIST_ID" \
  "${AUTH[@]}" \
  -H "Content-Type: application/json" \
  -d "$(jq -n --arg image "$SPOTIFY_IMAGE_URL" --arg url "$SPOTIFY_PROFILE_URL" \
        '{image: $image, profileUrls: {SPOTIFY: $url}}')"
```

This single endpoint replaces what the chat tool chain runs as four separate MCP calls (`update_account_info` ×2, `update_artist_socials` ×2). Add `label` to the body only after verifying it in the research (step 4). See [Update Artist](https://docs.recoupable.dev/api-reference/artists/update) for the full body schema.

**After this step:** tick `- [ ] 3.` → `- [x] 3.`.

## Step 4: Run Recoup research

Resolve the verified Spotify artist to an opaque Recoup research ID, then read
available profile fields, activity and playlist placements. Coverage varies by
artist and platform: an empty successful response is a data gap, not a request to
warm a cache. Use a web search for biography, press and collaborations.

The four structured reads below cost $0.05 each when successful, or $0.20 total
(200,000 micro-dollar credits). Web research is billed separately; check the
current credit reference before running it. Check HTTP status before extracting
IDs or making dependent calls. This helper
allows at most three attempts for network failures or `5xx`, each with a
90-second timeout. A `402` surfaces `billingUrl`; a `429` stops the step and prints
`Retry-After` for a later manual retry; it does not wait or retry automatically. Leave the step unchecked on errors.

```bash
BASE="https://api.recoupable.dev/api"
research_request() {
  local path=$1 response body status attempt headers retry_after
  shift
  headers=$(mktemp) || return 1
  for attempt in 1 2 3; do
    if response=$(curl -sS --max-time 90 -D "$headers" -w $'\n%{http_code}' \
      "${AUTH[@]}" "$BASE/research/$path" "$@"); then
      status=${response##*$'\n'}
      body=${response%$'\n'*}
      case "$status" in
        200) rm -f "$headers"; printf '%s' "$body"; return 0 ;;
        402)
          rm -f "$headers"
          printf 'Research needs credits: %s\n' \
            "$(printf '%s' "$body" | jq -r '.billingUrl // .error // "Check billing"')" >&2
          return 1 ;;
        429)
          retry_after=$(awk 'tolower($1)=="retry-after:" {sub(/^[^:]*:[[:space:]]*/, ""); sub(/\r$/, ""); print}' "$headers")
          rm -f "$headers"
          printf 'Research step stopped. Retry-After: %s. Resume manually when allowed.\n' "${retry_after:-not provided}" >&2
          return 1 ;;
        5??) ;;
        *) rm -f "$headers"; printf 'Research HTTP %s; resolve before resuming.\n' "$status" >&2; return 1 ;;
      esac
    fi
    if [ "$attempt" -lt 3 ]; then sleep "$((attempt * 2))"; fi
  done
  rm -f "$headers"
  echo 'Research unavailable after three attempts.' >&2
  return 1
}
```

### 4a: Resolve the research artist ID

```bash
LOOKUP=$(research_request lookup --get \
  --data-urlencode "spotifyId=$SPOTIFY_ARTIST_ID") || exit 1

RESEARCH_ARTIST_ID=$(printf '%s' "$LOOKUP" | jq -r \
  '[.id, .artist_info.id] | map(select(type == "string") | select(test("\\S"))) | first // empty')
```

Save this value as `researchArtistId`. It is separate from the Recoup artist
account UUID and Spotify ID. Do not coerce it to a number or copy an older
saved research ID into it; re-resolve and verify older workspaces. If a successful
lookup has no usable `id`, record that gap and skip 4b–4d, then run 4e.

See [Artist Lookup](https://docs.recoupable.dev/api-reference/research/lookup) for the response contract.

### 4b–4d: Read available structured signals

```bash
if [ -n "$RESEARCH_ARTIST_ID" ]; then
  PROFILE=$(research_request profile --get \
    --data-urlencode "id=$RESEARCH_ARTIST_ID") || exit 1
  CAREER=$(research_request career --get \
    --data-urlencode "id=$RESEARCH_ARTIST_ID") || exit 1
  PLAYLISTS=$(research_request playlists --get \
    --data-urlencode "id=$RESEARCH_ARTIST_ID") || exit 1
fi
```

Inspect the fields actually returned. Profile data may be partial; `career` is
an available activity feed, not a guaranteed complete career timeline.
`playlists` returns `placements`, which may be empty. Do not turn missing data
into zero or infer a label, achievement or placement that is absent.

See [Artist Profile](https://docs.recoupable.dev/api-reference/research/profile),
[Artist Activity](https://docs.recoupable.dev/api-reference/research/career) and
[Playlist Placements](https://docs.recoupable.dev/api-reference/research/playlists).

### 4e: Search for narrative, press and collaborations

```bash
RESEARCH_WEB=$(research_request web -X POST \
  -H "Content-Type: application/json" \
  -d "$(jq -n --arg name "$ARTIST_NAME" \
        '{query: ($name + " biography press recent collaborations")}')") || exit 1
```

Verify source links before asserting a fact. See [Web Search](https://docs.recoupable.dev/api-reference/research/web).

**After this step:** persist `researchArtistId` when available. Save the returned
payloads, source links and coverage gaps under `## Research` in `RECOUP.md`, with
one subsection per request. Tick step 4 only after the successful reads and any
intentional structured-data skip have been recorded.

## Step 5: Pull the Spotify catalog

```bash
TOP_TRACKS=$(curl -sS -G "https://api.recoupable.dev/api/spotify/artist/topTracks" \
  "${AUTH[@]}" \
  --data-urlencode "id=$SPOTIFY_ARTIST_ID")

ALBUMS=$(curl -sS -G "https://api.recoupable.dev/api/spotify/artist/albums" \
  "${AUTH[@]}" \
  --data-urlencode "id=$SPOTIFY_ARTIST_ID")

# For each notable album, drill in (ALBUM_ID from $ALBUMS):
ALBUM_DETAIL=$(curl -sS -G "https://api.recoupable.dev/api/spotify/album" \
  "${AUTH[@]}" \
  --data-urlencode "id=$ALBUM_ID")
```

`$SPOTIFY_ARTIST_ID` is the value you exported in step 2.

See [Spotify Top Tracks](https://docs.recoupable.dev/api-reference/spotify/artist-top-tracks), [Spotify Artist Albums](https://docs.recoupable.dev/api-reference/spotify/artist-albums), and [Spotify Album](https://docs.recoupable.dev/api-reference/spotify/album) for full schemas.

**After this step:** populate the artist's `releases/` folder — write one `releases/{release-slug}/RELEASE.md` per album (album slugs are bare, `-ep` / `-single` / `-compilation` suffixes for other types) using the per-album `GET /api/spotify/album?id=$ALBUM_ID` response, and write the top tracks snapshot to `releases/top-tracks.md`. `RELEASE.md` is the 18-section master release-management document (project snapshot, identifiers, narrative, audience, DSP strategy, marketing, social, PR, visuals, physical/merch/touring, team, budget, KPI tracking, links hub, plus Outstanding Deliverables and a Document History log) — Step 5 fills the Spotify-derivable fields and leaves the rest as `⚠️ TBD`. The full template + the field-by-field Spotify mapping live in the [`artist-workspace`](https://github.com/recoupable/recoup-platform-plugin/tree/main/skills/artist-workspace) skill at `references/release-template.md`. Tick `- [ ] 5.` → `- [x] 5.`.

## Step 6: Search the web for additional socials

```bash
SOCIALS_SEARCH=$(curl -sS -X POST "https://api.recoupable.dev/api/research/web" \
  "${AUTH[@]}" \
  -H "Content-Type: application/json" \
  -d "$(jq -n --arg name "$ARTIST_NAME" \
        '{query: ($name + " official instagram tiktok twitter youtube")}')")
```

Parse the results to extract any new social URLs whose host matches the platform reference table below.

See [Web Search](https://docs.recoupable.dev/api-reference/research/web) for the request/response schema.

**After this step:** save the discovered URLs (one per platform) into the `## Notes` section so step 7 can read them without re-querying. Tick `- [ ] 6.` → `- [x] 6.`.

## Step 7: PATCH the artist with the discovered socials

```bash
curl -sS -X PATCH "https://api.recoupable.dev/api/artists/$ARTIST_ID" \
  "${AUTH[@]}" \
  -H "Content-Type: application/json" \
  -d '{
    "profileUrls": {
      "INSTAGRAM": "https://instagram.com/...",
      "TIKTOK": "https://tiktok.com/@...",
      "TWITTER": "https://x.com/...",
      "YOUTUBE": "https://youtube.com/@..."
    }
  }'
```

Only include keys for platforms you actually found URLs for. The PATCH preserves existing socials for keys you omit, so this is safe to run incrementally.

See [Update Artist](https://docs.recoupable.dev/api-reference/artists/update) for the full body schema.

**After this step:** tick `- [ ] 7.` → `- [x] 7.`.

## Step 8: Synthesize the knowledge base

Combine the available structured and web research (`## Research` section), the Spotify catalog (`releases/`), and the discovered socials into a comprehensive markdown report. Recommended sections:

- Artist biography and origin
- Discography highlights (top tracks + key albums)
- Spotify presence (genres, listener count, notable playlists)
- Social media footprint
- Recent activity / press
- Notable collaborations and achievements

Append the report to the same `RECOUP.md` you scaffolded in Step 0 — add it as a `## Knowledge base` section below the checklist. The path is:

```
artists/$ARTIST_SLUG/RECOUP.md
```

This dovetails with the [`artist-workspace`](https://github.com/recoupable/recoup-platform-plugin/tree/main/skills/artist-workspace) skill's filesystem conventions, so future sandbox sessions can read both the checklist and the KB without needing a hosted URL.

**After this step:** tick `- [ ] 8.` → `- [x] 8.`. With every box ticked, the artist is fully set up.

## Platform key reference

`profileUrls` keys are **uppercase** platform identifiers, inferred from the URL host. Recognized platforms:

| URL contains | `profileUrls` key |
| --- | --- |
| `spotify.com` | `SPOTIFY` |
| `instagram.com` | `INSTAGRAM` |
| `tiktok.com` | `TIKTOK` |
| `x.com`, `twitter.com` | `TWITTER` |
| `youtube.` | `YOUTUBE` |
| `apple.com` | `APPLE` |
| `facebook.com` | `FACEBOOK` |
| `threads.net`, `threads.com` | `THREADS` |

URLs that don't match any of these are silently skipped — they won't be saved.

## What this workflow doesn't enforce

There's no server-side orchestrator forcing each step to run in order — the chain is honor-system. The `RECOUP.md` checklist is what gives you determinism in practice: as long as you tick boxes and persist values after each step, a fresh turn (or a different agent) can pick up exactly where the last one stopped. If you skip a checkbox or skip the persist, the next turn won't know that step ran and may either redo it or, worse, treat downstream calls as ready when they aren't.

A few constraints to honor:

- **Run steps in order.** The frontmatter values written at one step are the inputs for later steps.
- **Don't continue past a 4xx/5xx without recovery.** Leave the box unchecked, write the error to `## Notes`, and resolve before resuming.
- **Treat the file as the source of truth.** If something isn't on disk, don't assume it ran.
