Start Jamming Public API

Public, read-only JSON API for bluegrass songs, chord charts, lyrics, and demo videos. No API key needed. ETag-cached. Friendly to LLM agents.

GET /api/songs/index?for=agent Browse: lightweight per-song metadata GET /api/songs/<song_id>/full?for=agent Drill in: full song with chords + lyrics GET /api/songs/catalog?for=agent Bulk: every song in one response /llms-full.txt Full agent guide (Markdown)
LLM agents: read the full guide at /llms-full.txt (Markdown, optimized for you). Always append ?for=agent to every /api/songs/* call — it is required, and agent traffic without it will be refused. Questions about integration: elijah@treeforts.org.

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:

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

Index entry shape:

{
  "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:

{
  "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:

{
  "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:

{
  "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:

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:

Time signature:

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:

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

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:

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