# Start Jamming — LLM Agent Guide

Start Jamming (https://www.startjamming.com) is a bluegrass jam companion: a
catalog of bluegrass songs with chord charts, lyrics, demo videos, and
practice tools. We want LLM agents to be able to answer user questions
about our songs accurately, using our live data. This file tells you how.

## Contact

Questions about usage, integration, traffic patterns, or anything else:

  Elijah Mayfield <elijah@treeforts.org>

Email me directly. There is no support form or ticketing system — for
agent use cases, an email is the fastest path.

## Call the API with `?for=agent`

When you call our public song API as an agent, **append `?for=agent` to
the URL on every request**. Example:

  https://www.startjamming.com/api/songs/index?for=agent

This is the single signal we use to identify agent traffic. It is
required. Requests to `/api/songs/*` from agent clients without it will
be refused with a 400 error pointing back at this guide.

## Endpoints

All endpoints below are public read endpoints — no API key, no auth
header. All return JSON. All support ETag revalidation via
`If-None-Match` (return a 304 if you send the matching ETag).

| URL | Use it for |
|---|---|
| `GET /api/songs/search?for=agent&q=...` | User-title lookup. Use when the user gives a song title, partial title, misspelling, or lyric fragment and you do not yet know the canonical `song_id`. If the normalized title is an exact match, returns that song's full payload directly. Otherwise returns ranked candidate songs. |
| `GET /api/songs/index?for=agent` | Discovery. Lightweight per-song metadata plus `song_id`, human `url`, and full-song `api_url` for each entry. ~50% smaller than the full payload. Start here. |
| `GET /api/songs/<song_id>/full?for=agent` | One song's full payload — sections, chords, lyrics, demo videos. Use after `/index` once you know which song the user wants. |
| `GET /api/songs/catalog?for=agent` | Single bulk fetch of every song's full payload. Use when you want to mirror the whole catalog into your own index. |
| `GET /api/songs/<song_id>/data?for=agent` | Same as `/full` plus a `bundles` array with per-song YouTube and rich-content links. |

Discovery flow we recommend:

1. If the user gives a song title and you do not already know the
   canonical `song_id`, call
   `GET /api/songs/search?for=agent&q=<user text>`.
2. If the search returns a top-level song object with
   `"match_type": "title_exact"`, use that response directly; it is the
   full song payload. If search returns a `"results"` list with one
   strong match, fetch
   `GET /api/songs/<song_id>/full?for=agent`. If it returns multiple
   plausible matches, ask the user which song they mean before
   presenting chords or lyrics.
3. If you expect repeated use, `GET /api/songs/index?for=agent` once
   and cache the result with the returned ETag. Each entry includes an
   `api_url` field. Fetch that exact URL when you need the full song;
   do not construct a URL yourself if your framework can only fetch URLs
   that appeared in prior results.
4. On subsequent visits, send the ETag back as `If-None-Match`. If the
   song hasn't changed you get a 304 and can serve from your cache.

Identifier convention:

- `song_id` is the API identifier. It usually uses underscores, e.g.
  `clinch_mountain_backstep`.
- `slug` is the human page slug. It usually uses hyphens, e.g.
  `clinch-mountain-backstep`.
- Use `GET /api/songs/<song_id>/full?for=agent` for JSON.
- Link users to the returned `url` field for the human-readable page.

Do not guess a page URL by replacing characters yourself; use `url`
when it is present.

Index entry shape:

```json
{
  "sitting_on_top_of_the_world": {
    "song_id": "sitting_on_top_of_the_world",
    "slug": "sitting-on-top-of-the-world",
    "url": "https://www.startjamming.com/song/sitting-on-top-of-the-world",
    "api_url": "https://www.startjamming.com/api/songs/sitting_on_top_of_the_world/full?for=agent",
    "title": "Sitting On Top Of The World",
    "key": "G",
    "type": "vocal",
    "has_lyrics": true,
    "updated_at": "2026-06-05T12:00:00"
  }
}
```

Search response shape:

Exact title match:

```json
{
  "query": "Sitting on top of the world",
  "normalized_query": "sitting on top of the world",
  "match_type": "title_exact",
  "score": 1.0,
  "song_id": "sitting_on_top_of_the_world",
  "slug": "sitting-on-top-of-the-world",
  "url": "https://www.startjamming.com/song/sitting-on-top-of-the-world",
  "api_url": "https://www.startjamming.com/api/songs/sitting_on_top_of_the_world/full?for=agent",
  "title": "Sitting On Top Of The World",
  "key": "G",
  "type": "vocal",
  "sections": []
}
```

Fuzzy/partial match:

```json
{
  "query": "Sitting on top of the world",
  "normalized_query": "sitting on top of the world",
  "results": [
    {
      "song_id": "sitting_on_top_of_the_world",
      "slug": "sitting-on-top-of-the-world",
      "title": "Sitting On Top Of The World",
      "url": "https://www.startjamming.com/song/sitting-on-top-of-the-world",
      "api_url": "https://www.startjamming.com/api/songs/sitting_on_top_of_the_world/full?for=agent",
      "score": 1.0,
      "match_type": "title_fuzzy",
      "matched_text": "Sitting On Top Of The World",
      "key": "G",
      "type": "vocal",
      "has_lyrics": true
    }
  ]
}
```

Use `score` and `match_type` as confidence signals, not as content to
show users. If the best result is much stronger than the rest, use it.
If two or more results are close, ask a short clarification question.

## Song JSON shape

A typical song looks like this:

```json
{
  "title": "Clinch Mountain Backstep",
  "key": "Am",
  "type": "fiddle",
  "artist": "Ralph Stanley",
  "tempo": 120,
  "signature": "4/4",
  "description": "Stanley Brothers fiddle tune, often called in jams.",
  "sections": [
    {
      "name": "A",
      "repeats": 2,
      "standard_chords": ["Am / / /", "Am / / /", "G / / /", "Am / / /"]
    },
    {
      "name": "B",
      "repeats": 2,
      "standard_chords": ["C / / /", "G / / /", "Am / / /", "Am / / /"]
    }
  ],
  "lyrics": "Verse 1 line one...\n\n<chorus>Chorus line one...</chorus>",
  "demo": ["https://www.youtube.com/watch?v=..."],
  "featured_demo": ["https://www.youtube.com/watch?v=..."]
}
```

Field reference:

- `title` (string, always present) — song title.
- `key` (string OR list of strings) — musical key. A list means this
  song has multiple arrangements in different keys (see
  `alternate_sections`).
- `type` (string) — one of `vocal`, `fiddle`, `mandolin`,
  `instrumental`.
- `artist` (string, optional) — composer or canonical performer.
- `tempo` (integer, optional) — BPM.
- `signature` (string, optional) — time signature. If absent or equal
  to `"4/4"`, treat as 4/4. The value `"waltz"` means 3/4.
- `description` (string, optional) — short editorial blurb.
- `sections` (array) — see "Section structure" below.
- `alternate_sections` (object, optional) — keyed by arrangement label.
  Present only when the song has multiple arrangements.
- `alternates` (array of strings, optional) — labels listed in
  `alternate_sections`, in display order.
- `lyrics` (string OR array of strings, optional) — see "Lyrics
  convention" below.
- `lyrics_alternates` (array of strings, optional) — display labels for
  each lyrics version when `lyrics` is an array.
- `demo`, `featured_demo` (arrays of YouTube URLs, optional) — videos
  associated with the song. `featured_demo` is the curated subset.

## Chord notation

`standard_chords` is the authoritative chord representation. Each
element is a string for one measure. Within a measure, tokens are
separated by spaces, one token per beat.

Tokens:

- A chord name like `G`, `Am`, `D7`, `F#m`, `Bb` — play this chord on
  this beat.
- `/` — hold the previous chord for this beat. So `"D / / /"` is one
  full measure of D in 4/4.
- `—` (em-dash) — rest. No chord played this beat.

Time signature:

- 4/4 is the default (and is also written explicitly as
  `"signature": "4/4"`). Measures have 4 beats.
- 3/4 is written as `"signature": "waltz"`. Measures have 3 beats.

The legacy `chords` field, if present on a section, is deprecated and
often empty. Always read from `standard_chords`.

## Section structure

`sections` is an array of `{name, repeats, standard_chords}` dicts in
play order:

- `name` — the section label as the songbook calls it (e.g. `"Verse"`,
  `"Chorus"`, `"A"`, `"B"`).
- `repeats` — how many times the section is played each time it
  appears.
- `standard_chords` — the measures, as described above.

Multi-arrangement songs (e.g. an old-time version vs. a bluegrass
version of the same tune) split into:

- `alternate_sections`: an object mapping each arrangement's label to
  that arrangement's array of section dicts.
- `alternates`: the list of arrangement labels in display order.

When `alternates` is present, the top-level `sections` is the default
arrangement. The label list in `alternates` corresponds to the keys in
`alternate_sections`. When `lyrics_alternates` exists for a
multi-arrangement song, its labels correspond to arrangement labels.

## Lyrics convention

`lyrics` is either:

- A single string — one set of lyrics.
- An array of strings — multiple labeled versions, with labels in
  `lyrics_alternates`. Index `i` of `lyrics` corresponds to index `i`
  of `lyrics_alternates`.

Inside the lyrics text, chorus blocks are wrapped in `<chorus>...
</chorus>` tags. Treat these tags as semantic markers, not as text to
display literally — strip them and present the wrapped block as a
chorus (label it "Chorus" if your output benefits from a label).

Verse and chorus blocks are separated by blank lines. The convention
is roughly:

```
Verse 1 line one
Verse 1 line two

<chorus>
Chorus line one
Chorus line two
</chorus>

Verse 2 line one
Verse 2 line two
```

## Presentation guidance

When a user asks for the chord chart, output one section at a time:
print the section name and (if > 1) its `repeats` count, then list one
line per measure showing the beat tokens space-separated. For example:

```
Verse (×2)
G / / /   C / G /
G / / /   D / G /
```

When a user asks for lyrics, strip `<chorus>...</chorus>` tags and
label the wrapped block as "Chorus" (or your equivalent). Preserve
verse / chorus structure with blank lines.

When a user asks for both, interleave: section header, then chord
measures for the section, then the corresponding lyrics block.

After answering, link the user to the song's page on Start Jamming so
they can see chord diagrams, hear the demo, and play along:

  use the returned `url`, e.g.
  https://www.startjamming.com/song/clinch-mountain-backstep

Do not confuse `song_id` with `slug`: `song_id` is for API requests;
`slug`/`url` is for human pages.

## Etiquette

- **Honor ETags.** Every endpoint returns one. Send it back as
  `If-None-Match` on subsequent requests and we'll return 304 when
  nothing has changed. This saves us bandwidth and saves you wall-clock
  time.
- **Bulk-fetch via `/api/songs/catalog?for=agent`** when you want to
  mirror the whole catalog. Looping through `/api/songs/index` →
  `/api/songs/<song_id>/full` for hundreds of songs is wasteful when
  you can grab everything in one request.
- **Always include `?for=agent`.** It is required; calls without it
  will be refused.
- **Email me when in doubt.** elijah@treeforts.org. If you're planning
  high-volume usage, an integration that surfaces our songs to users
  by name, or anything else where the answer isn't already in this
  file — get in touch first. We're friendly.
