← Groove First
API
Tokens

Drive Groove First from your own code

The two metered tasks are available over HTTP. A natural use is a beat-making app, a practice tool or a chat assistant that already holds a drum pattern: your code sends the loop and the vibe, and gets back either a producer's verdict on the loop - keep, tweak or rebuild, with all seven rows - or a style prompt for an AI music generator written from the loop.

The split first: the page measures the loop; the model judges and writes; the page checks it. The facts (tempo, swing, four on the floor, backbeat, half-time, hat grid, syncopation, the bass against the key, the closest pattern families) come from groovekit.js, which runs in node as well as in the browser. Its checkGroove checks a groove reply before anything is applied: every row must parse as 16 steps, rows the reply leaves out keep the person's row, a locked tempo stays, a key change is only allowed on a rebuild, the verdict must match how many cells really changed, and every change note must point at cells that differ. checkPrompt checks the tempo written in the prompt against the loop, the character limit, artist comparisons, words both asked for and excluded, and every cited fact id.

Base URL and the envelope

Every endpoint lives under https://api.skillsafe.ai/v1/app-api, and every response uses one envelope:

{ "ok": true,  "data":  { ... } }
{ "ok": false, "error": { "code": "...", "message": "...", "status": 402, "details": { ... } } }

The token is minted for this app (the guest call takes {"slug":"groove-first"}), so no slug header is needed afterwards. Send it as Authorization: Bearer …. The run body is the input object, sent bare: never wrap it as {"input": {…}}.

StatusCodeMeaning
400validation_errorThe body is not a JSON object, or task is missing.
401unauthorizedNo token, or a stale one. Mint a guest token or sign in again.
402insufficient_creditsThe balance is under min_credits. Price with /estimate first.
403forbiddenA guest token tried to run: both tasks are metered and need a personal token.
404not_foundUnknown job id.
429rate_limitedBack off and retry.
5xxserver_errorTransient. Retry with the same Idempotency-Key so a retry never double-bills.

The task field comes first

Every run sends task. There are two, and the page runs them in this order on the same loop:

taskDoesReply
grooveKeeps, tweaks or rebuilds the loop for the vibe.verdict (keep - at most 3 cells change; tweak - only the cells that need it; rebuild - a new loop, the only verdict that may change the key; an empty grid is always a rebuild), verdict_why, title, bpm, swing, key, pattern {kick, snare, clap, hat, ohat, perc, bass}, changes [{row, steps, what, why, cite}], feel, caveats.
promptWrites a style prompt for an AI music generator from the loop.title, style_prompt (states <bpm> BPM, within limit), exclude, structure [{tag, bars, note}], tags [{tag, cite}], variations, caveats.

Pattern rows: drum rows are 16 characters, X accent, x soft, . rest. The bass row is 16 space-separated tokens: a note with an octave (C2, Eb1), - to hold the previous note, . to rest. Cites are the fact ids in facts plus N-vibe.

The declared input schema carries scalars only, so object-valued fields travel JSON-encoded as strings (GrooveKit.toWire does this). The schema requires task, bpm, key, grid and facts (every run sends them, never empty); the lane-specific fields, vibe and swing are declared but optional, because an empty string or a zero is a legitimate value there.

Input fields

FieldTypeNotes
taskstringgroove or prompt.
vibestringThe person's words; past 600 characters the middle is cut around a marker.
bpmnumber40-200.
swingnumber0-60; 50 is a full triplet shuffle.
keystringLike C minor, A major, D dorian.
gridstringThe loop as text, one row per line: kick X...X...X...X... … bass F1 . . . F1 . . . …
factsstring (JSON)[{id, text}] from GrooveKit.facts: F-bpm, F-swing, F-key, F-density, F-4otf, F-backbeat, F-halftime, F-dembow, F-hats, F-ohat, F-sync, F-bass, F-bass-out, F-family, F-empty - only the ones that hold.
bpm_lockedstringgroove only: yes keeps the tempo.
vocalsstringprompt only: instrumental or a description of the voice.
avoidstringprompt only, optional.
limitnumberprompt only: the generator's style-field limit in characters (120-3000; the page defaults to 1000, an estimate).
retry_notestringOnly on a reformat retry.

The reply

The job's output.output is a string holding one JSON object. Parse it with GrooveKit.parseReply, then normalize(task, reply), then check it: checkGroove(reply, loop, {lockBpm}) returns {proposed, diff, notes, analysis} - the loop the page would apply, the changed cells and the notes (ok, info, warn, bad); checkPrompt(reply, loop, input) returns {notes, length, limit}.

Worked examples

Replies produced against the app's prompt for the sample loops (made up). Between them the groove samples cover all three verdicts; the prompt samples are written from the loop after its groove reply is applied. The page replays each of them free from its sample buttons; the full replies ship in example-result.js. Long fields are folded.

task: groove - Rainy-window lo-fi - keep

Input:

{
 "task": "groove",
 "vibe": "Rainy-window lo-fi for studying: lazy swung drums, a soft dusty snare, a warm bass that mostly sits on the root. Nothing busy.",
 "bpm": 78,
 "swing": 30,
 "key": "A minor",
 "grid": "kick  X.....x...X.....\nsnare ....x.......x...\nclap  ................\nhat   x.x.x.x.x.x.x.x.\nohat  ..............x.\nperc  ...........x....\nbass  A1 - - . . . A1 . E1 - - . . . A1 .",
 "facts": "[{\"id\": \"F-bpm\", \"text\": \"Tempo 78 BPM\"}, {\"id\": \"F-swing\", \"text\": \"Swing 30% (50 is a full triplet shuffle)\"}, {\"id\": \"F-key\", \"text\": \"Key A minor\"}, \"... 6 more facts\"]",
 "bpm_locked": "yes"
}

Reply:

{
 "task": "groove",
 "verdict": "keep",
 "verdict_why": "The tempo and swing already sit in a lazy, dragging pocket (F-bpm, F-swing), the snare on beats 2 and 4 is a soft hit rather than an accent so it already reads as dusty, and the bass already leans on the root three times out of four (F-bass) - the page's own heuristic already calls this lo-fi (F-family), which is exactly the vibe asked for (N-vibe).",
 "title": "Rainy Window Lo-Fi",
 "bpm": 78,
 "swing": 30,
 "key": "A minor",
 "pattern": {
  "kick": "X.....x...X.....",
  "snare": "....x.......x...",
  "clap": "................",
  "hat": "x.x.x.x.x.x.x.x.",
  "ohat": "..............x.",
  "perc": "...........x....",
  "bass": "A1 - - . . . A1 . E1 - - . . . A1 ."
 },
 "changes": [],
 "feel": [
  "The kick sits on 1 then leans into the 'and' of 2 and the 'and' of 3 - that lazy lean, not a strict stomp, is what makes it feel unhurried.",
  "Swing at 30% drags the off-beat hats and the bass re-attacks late, which is where the sleepy, rainy-day feel lives.",
  "The snare is a soft hit, not an accent, so it sits back in the mix - that softness is the 'dusty' the vibe asked for.",
  "..."
 ],
 "caveats": [
  "Actual dust/vinyl-crackle texture comes from the synth or any sample layer the page adds, not from this step grid - the pattern just leaves room for it."
 ]
}

task: groove - Sunday deep house - tweak

Input:

{
 "task": "groove",
 "vibe": "Sunday-afternoon deep house: warm, open hats on the offbeat, a rolling offbeat bassline that keeps moving. Keep the four-on-the-floor kick and the clap.",
 "bpm": 122,
 "swing": 0,
 "key": "F minor",
 "grid": "kick  X...X...X...X...\nsnare ................\nclap  ....X.......X...\nhat   x.x.x.x.x.x.x.x.\nohat  ................\nperc  ................\nbass  F1 . . . . . . . F1 . . . . . . .",
 "facts": "[{\"id\": \"F-bpm\", \"text\": \"Tempo 122 BPM\"}, {\"id\": \"F-swing\", \"text\": \"Straight - no swing\"}, {\"id\": \"F-key\", \"text\": \"Key F minor\"}, \"... 7 more facts\"]",
 "bpm_locked": "yes"
}

Reply:

{
 "task": "groove",
 "verdict": "tweak",
 "verdict_why": "The kick is already four-on-the-floor and the clap already holds beats 2 and 4 (F-4otf, F-backbeat), which is the house backbone the vibe asks to keep, and F-family already reads house. What's missing is any open hat at all and a bassline that does more than hit the root twice (F-bass), so this needs targeted additions, not a rebuild.",
 "title": "Sunday Afternoon Roller",
 "bpm": 122,
 "swing": 0,
 "key": "F minor",
 "pattern": {
  "kick": "X...X...X...X...",
  "snare": "................",
  "clap": "....X.......X...",
  "hat": "x.x.x.x.x.x.x.x.",
  "ohat": "..x...x...x...x.",
  "perc": "................",
  "bass": "F1 . Ab1 . . . Bb1 . F1 . C2 . . . Bb1 ."
 },
 "changes": [
  {
   "row": "ohat",
   "steps": [
    3,
    7,
    11,
    15
   ],
   "what": "added open hihat hits on the upbeat offbeats",
   "why": "the vibe asks for warm open hats on the offbeat, but the ohat row was empty; placing them on the upbeat eighths (the \"and\" of each beat) answers that directly without touching the existing closed-hat pulse",
   "cite": [
    "N-vibe"
   ]
  },
  {
   "row": "bass",
   "steps": [
    3,
    7,
    11,
    15
   ],
   "what": "added Ab1, Bb1, C2 and Bb1 between the two existing root hits",
   "why": "the vibe wants a rolling bassline that keeps moving, but F-bass shows only two static root hits one bar apart; these offbeat notes stay inside F minor and walk between the roots instead of repeating them",
   "cite": [
    "N-vibe",
    "F-bass"
   ]
  }
 ],
 "feel": [
  "Downbeat weight still comes from the four-on-the-floor kick (1, 5, 9, 13) and the clap on 2 and 4 - that part was already right, so don't rebalance it.",
  "The open hat now answers on every upbeat (3, 7, 11, 15); let it ring a touch longer than the closed hat for the warm, airy Sunday-afternoon feel instead of a tight tech-house flick.",
  "The bass walks F1 - Ab1 - Bb1 - F1 - C2 - Bb1 across the bar instead of just stabbing the root twice - that movement is what makes it roll.",
  "..."
 ],
 "caveats": [
  "The open hat sits on the same steps as the existing closed hat (3, 7, 11, 15); if your synth lets both ring together, choke the closed hat there so the open hat reads clearly."
 ]
}

task: groove - Trap vibe, house beat - rebuild

Input:

{
 "task": "groove",
 "vibe": "Dark trap at 140: half-time snare on 3, rolling 16th hi-hats with a few stutters, a booming sliding 808 bass. Menacing.",
 "bpm": 124,
 "swing": 0,
 "key": "C minor",
 "grid": "kick  X...X...X...X...\nsnare ....X.......X...\nclap  ................\nhat   ..x...x...x...x.\nohat  ................\nperc  ................\nbass  C2 . . . C2 . . . C2 . . . C2 . . .",
 "facts": "[{\"id\": \"F-bpm\", \"text\": \"Tempo 124 BPM\"}, {\"id\": \"F-swing\", \"text\": \"Straight - no swing\"}, {\"id\": \"F-key\", \"text\": \"Key C minor\"}, \"... 7 more facts\"]",
 "bpm_locked": "no"
}

Reply:

{
 "task": "groove",
 "verdict": "rebuild",
 "verdict_why": "The grid is a straight four-on-the-floor house/pop loop (F-family, F-4otf) with a backbeat on 2 and 4 (F-backbeat) at 124 BPM (F-bpm); the vibe wants dark trap at 140 with a half-time snare on 3, rolling stuttered 16th hats and a sliding 808, which is a different family, tempo and kick/snare layout, so this needs a full rebuild, not edits.",
 "title": "Menacing Half-Time Bounce",
 "bpm": 140,
 "swing": 0,
 "key": "C minor",
 "pattern": {
  "kick": "X..X..X...X..X..",
  "snare": "........X.......",
  "clap": "................",
  "hat": "xxxxxxXXxxxxxXXX",
  "ohat": "................",
  "perc": "................",
  "bass": "C2 - - Eb2 - - G1 - C2 - Bb1 - - C2 - -"
 },
 "changes": [
  {
   "row": "kick",
   "steps": [
    4,
    5,
    7,
    9,
    11,
    13,
    14
   ],
   "what": "Dropped the four-on-the-floor kick for a syncopated pattern that steps aside at beat 3",
   "why": "Trap kicks don't hit every beat; leaving step 9 empty lets the half-time snare and 808 land without a kick fighting them",
   "cite": [
    "N-vibe",
    "F-4otf"
   ]
  },
  {
   "row": "snare",
   "steps": [
    5,
    9,
    13
   ],
   "what": "Removed the hits on beats 2 and 4 and placed a single accented hit on beat 3 (step 9) only",
   "why": "A snare on 3 alone, nowhere else, is the half-time feel the vibe is asking for",
   "cite": [
    "N-vibe",
    "F-backbeat"
   ]
  },
  {
   "row": "hat",
   "steps": [
    1,
    2,
    4,
    5,
    6,
    7,
    8,
    9,
    10,
    12,
    13,
    14,
    15,
    16
   ],
   "what": "Filled every 16th step with soft hits and layered two accent bursts, steps 7-8 and 14-16, over the top",
   "why": "Rolling 16ths means every step plays; the back-to-back accents read as the stutters the vibe asks for",
   "cite": [
    "N-vibe",
    "F-hats"
   ]
  },
  "..."
 ],
 "feel": [
  "The kick drops out on beat 3 so the snare and 808 land by themselves - that empty space is most of the menace.",
  "The hats never stop; listen for the two quick accent bursts (steps 7-8 and 14-16) as the stutters, not a tempo change.",
  "Follow the 808 by ear: it dips to G1, climbs back to C2 under the snare, dips lower to Bb1, then rises home - ride that shape instead of retriggering it.",
  "..."
 ],
 "caveats": [
  "A basic step synth triggers a new pitch per note rather than gliding; for an audible slide between the held bass notes you need an instrument with portamento."
 ]
}

task: prompt - Rainy-window lo-fi - instrumental prompt

Input:

{
 "task": "prompt",
 "vibe": "Rainy-window lo-fi for studying: lazy swung drums, a soft dusty snare, a warm bass that mostly sits on the root. Nothing busy.",
 "bpm": 78,
 "swing": 30,
 "key": "A minor",
 "grid": "kick  X.....x...X.....\nsnare ....x.......x...\nclap  ................\nhat   x.x.x.x.x.x.x.x.\nohat  ..............x.\nperc  ...........x....\nbass  A1 - - . . . A1 . E1 - - . . . A1 .",
 "facts": "[{\"id\": \"F-bpm\", \"text\": \"Tempo 78 BPM\"}, {\"id\": \"F-swing\", \"text\": \"Swing 30% (50 is a full triplet shuffle)\"}, {\"id\": \"F-key\", \"text\": \"Key A minor\"}, \"... 6 more facts\"]",
 "vocals": "instrumental",
 "avoid": "vocals, harsh cymbals",
 "limit": 1000
}

Reply:

{
 "task": "prompt",
 "title": "Rainy Dusty Study Groove",
 "style_prompt": "lo-fi hip-hop, boom bap, 78 BPM, swung laid-back groove, A minor, soft dusty snare, closed hi-hats, muted kick, warm root-note bass, mellow electric piano, rain-on-window ambience, vinyl crackle, tape hiss, calm study mood, instrumental",
 "exclude": "vocals, harsh cymbals, quantized rigid drums",
 "structure": [
  {
   "tag": "[Intro]",
   "bars": 4,
   "note": "Rain and vinyl crackle fade in, a lone electric piano holds a chord; no drums yet, so the groove lands clean when it enters."
  },
  {
   "tag": "[Groove]",
   "bars": 16,
   "note": "Full loop in: kick on 1, the soft syncopated push on 7, and 11; snare's soft backbeat sits on 2 and 4; hats keep straight 8ths swung 30%; bass holds root A on 1, dips to E, returns to root."
  },
  {
   "tag": "[Break]",
   "bars": 4,
   "note": "Kick and snare drop out, leaving only the swung hats and the root bass with the rain ambience, so the return feels like a breath."
  },
  "..."
 ],
 "tags": [
  {
   "tag": "78 BPM",
   "cite": [
    "F-bpm"
   ]
  },
  {
   "tag": "boom bap",
   "cite": [
    "F-family"
   ]
  },
  {
   "tag": "swung laid-back groove",
   "cite": [
    "F-swing"
   ]
  },
  "..."
 ],
 "variations": [
  "lo-fi hip-hop, boom bap, 78 BPM, swung laid-back groove, A minor, dark moody chords, soft dusty snare, closed hi-hats, deep sub-heavy bass on the root, muted kick, rain-on-window ambience, thick vinyl crackle, tape hiss, late-night mood, instrumental",
  "lo-fi hip-hop, 78 BPM, swung laid-back groove, A minor, soft dusty snare only on the backbeat, closed hi-hats, sparse root-note bass, no extra percussion, rain-on-window ambience, soft tape hiss, quiet study mood, instrumental",
  "lo-fi hip-hop, boom bap, 78 BPM, swung groove with extra syncopation, A minor, crisp dusty snare, closed hi-hats driving the swing, warm root-note bass with a walking passing note, light shaker percussion, rain-on-window ambience, vinyl crackle, upbeat focused mood, instrumental"
 ],
 "caveats": [
  "Most generators approximate swing feel and section structure rather than reproducing them exactly."
 ]
}

task: prompt - Sunday deep house - prompt with vocals

Input:

{
 "task": "prompt",
 "vibe": "Sunday-afternoon deep house: warm, open hats on the offbeat, a rolling offbeat bassline that keeps moving. Keep the four-on-the-floor kick and the clap.",
 "bpm": 122,
 "swing": 0,
 "key": "F minor",
 "grid": "kick  X...X...X...X...\nsnare ................\nclap  ....X.......X...\nhat   x.x.x.x.x.x.x.x.\nohat  ..x...x...x...x.\nperc  ................\nbass  F1 . Ab1 . . . Bb1 . F1 . C2 . . . Bb1 .",
 "facts": "[{\"id\": \"F-bpm\", \"text\": \"Tempo 122 BPM\"}, {\"id\": \"F-swing\", \"text\": \"Straight - no swing\"}, {\"id\": \"F-key\", \"text\": \"Key F minor\"}, \"... 8 more facts\"]",
 "vocals": "soulful female vocal, short chopped phrases",
 "avoid": "trance supersaws",
 "limit": 1000
}

Reply:

{
 "task": "prompt",
 "title": "Sunday Afternoon Roller",
 "style_prompt": "deep house, 122 BPM, four on the floor kick, straight groove, warm closed hi-hats, open hi-hats on the offbeat, backbeat clap, rolling offbeat bassline, warm sub-heavy bass, F minor, warm analog pads, soulful female vocal with short chopped phrases, laid-back sunday-afternoon mood, warm analog production",
 "exclude": "trance supersaws, aggressive industrial distortion, harsh digital clipping",
 "structure": [
  {
   "tag": "[Intro]",
   "bars": 8,
   "note": "four-on-the-floor kick and the rolling offbeat bass enter alone, warm pads fade in, no vocal yet"
  },
  {
   "tag": "[Verse]",
   "bars": 8,
   "note": "soulful vocal chops enter over the bassline, backbeat clap holds steady, hats stay closed"
  },
  {
   "tag": "[Chorus]",
   "bars": 8,
   "note": "open hi-hats widen on the offbeat, vocal phrases layer and repeat, the bassline pushes harder"
  },
  "..."
 ],
 "tags": [
  {
   "tag": "four on the floor kick",
   "cite": [
    "F-4otf"
   ]
  },
  {
   "tag": "straight groove",
   "cite": [
    "F-swing"
   ]
  },
  {
   "tag": "closed hi-hats",
   "cite": [
    "F-hats"
   ]
  },
  "..."
 ],
 "variations": [
  "deep house, 122 BPM, four on the floor kick, straight groove, warm closed hi-hats, open hi-hats on the offbeat, backbeat clap, rolling offbeat bassline, dark moody bass, F minor, brooding analog pads, soulful female vocal with short chopped phrases, late-night mood, warm analog production",
  "deep house, 122 BPM, four on the floor kick, straight groove, closed hi-hats, open hi-hats on the offbeat, backbeat clap, rolling offbeat bassline, warm sub-heavy bass, F minor, soulful female vocal with short chopped phrases, minimal sunday-afternoon mood",
  "deep house, 122 BPM, four on the floor kick, driving groove, warm closed hi-hats, open hi-hats on the offbeat, backbeat clap, rolling offbeat bassline, warm sub-heavy bass, F minor, warm analog pads, soulful female vocal with short chopped phrases, energetic sunday-afternoon mood, warm analog production"
 ],
 "caveats": [
  "Most generators won't guarantee exact hi-hat placement or key - treat F minor and the offbeat pattern as a strong hint, not a lock."
 ]
}

Build the input yourself

Load the script in node (global.window = {}; require("./groovekit.js")), then:

const G = GrooveKit;
const loop = G.sanitize(Object.assign(G.parseGrid(
  "kick  X...X...X...X...\nclap  ....X.......X...\nhat   x.x.x.x.x.x.x.x.\nbass  F1 . . . . . . . F1 . . . . . . ."
).pattern, { bpm: 122, swing: 0, key: "F minor" }));
const { input } = G.buildInput("groove", loop, { vibe: "warm deep house, open hats on the offbeat", lockBpm: true });
const wire = G.toWire(input);       // POST this to /estimate and /run
// later: const n = G.normalize("groove", G.parseReply(job.output.output));
// const { proposed, diff, notes } = G.checkGroove(n, loop, { lockBpm: true });
// G.toMidi(proposed, 4) -> Uint8Array of a .mid file

Step 1 — a tiny client helper

One function that sends JSON and unwraps the envelope. Paste a personal token from /tokens.html, or leave it empty and mint a guest token in step 2.

BASE="https://api.skillsafe.ai/v1/app-api"
TOKEN="paste-your-token-here"   # from https://groove-first.skillsafe.ai/tokens.html
api() { # api METHOD PATH [JSON-BODY]
  curl -s -X "$1" "$BASE$2" -H "Content-Type: application/json" \
    ${TOKEN:+-H "Authorization: Bearer $TOKEN"} ${3:+--data "$3"}
}

Step 2 — a token

A guest token is enough for /me and /estimate. A run is metered, so it needs a personal token: sign in on /tokens.html and copy it from there.

# a guest token (enough for /me and /estimate); a run needs your personal token from /tokens.html
TOKEN=$(curl -s -X POST "$BASE/guest" -H "Content-Type: application/json" -d '{"slug":"groove-first"}' | jq -r '.data.token')

Step 3 — GET /me

Who the token is (subject_type: user or guest) and the credit balance.

api GET /me | jq '.data | {subject_type, credits}' 

Step 4 — POST /estimate

Free. The body is the run input itself, the bare object (never wrapped in {"input": ...}). hold_credits is a reservation, not the price; min_credits is what the balance must cover; model_alias reads gpt-terra.

# input.json: the run input object - {"task": "groove", "vibe": "...", "bpm": 122, "swing": 0, "key": "F minor", "grid": "kick  X...X...X...X...\n...", "facts": "[...]", "bpm_locked": "yes"}
api POST /estimate "$(cat input.json)" | jq '.data | {hold_credits, min_credits, model, model_alias, markup_bps, warnings}' 

Step 5 — POST /run, then poll the job

Send an Idempotency-Key built from the lane and the input: a retry with the same key never bills twice. The page uses groove-first:<task>:<inputHash>:a<attempt> - the task is part of the key, so the groove and prompt runs over the same loop never collide, and its one reformat retry (with retry_note added) is attempt 2, a new key and a second run. The job's output.output is the reply text; truncated: true means the balance cut it short.

KEY="groove-first:groove:$(shasum -a 256 input.json | cut -c1-16):a1"
JOB=$(curl -s -X POST "$BASE/run" -H "Content-Type: application/json" -H "Authorization: Bearer $TOKEN" \
  -H "Idempotency-Key: $KEY" --data @input.json | jq -r '.data.job_id')
while :; do
  J=$(api GET "/jobs/$JOB"); S=$(echo "$J" | jq -r '.data.status')
  case "$S" in succeeded|failed|cancelled) break;; esac; sleep 2
done
echo "$J" | jq -r '.data.output.output' > reply.json   # one JSON object, the reply contract
echo "$J" | jq '.data | {charged_credits, truncated}' 

Step 6 — POST /run-stream

The same run as Server-Sent Events, with the same input.json. Browsers receive progress ticks rather than text deltas; the final done event carries the reply.

# Server-Sent Events: tick/delta events while the job runs, then a final event with the job
curl -N -s -X POST "$BASE/run-stream" -H "Content-Type: application/json" -H "Authorization: Bearer $TOKEN" \
  -H "Idempotency-Key: groove-first:groove:$(shasum -a 256 input.json | cut -c1-16):a1" --data @input.json