Konnect API

Upload, price and publish your beats from your own scripts. Your catalogue is yours — move it in and out whenever you like.

Get an API key

You don't need to write code to automate uploads. Most producers use the watch folder — point it at the folder you export to and new beats upload themselves, with subfolder names setting the type-beat tags. This page is for the rest: if you already automate things, everything below is yours.

Authentication

Send your key on every request. Either header works:

X-API-Key: kn_live_...
Authorization: Bearer kn_live_...

Keys are shown once when created and stored only as a hash, so we cannot recover one for you — mint a new key and revoke the old one. Create and revoke keys on your Store page.

Scopes

ScopeGrants
beats:readRead your listings.
beats:writeCreate, update, publish and delete listings.
scrape:readReserved. Not yet accepted by any endpoint.
scrape:writeReserved. Not yet accepted by any endpoint.

Uploading a beat

Four calls: create the listing, ask where the audio goes, send the bytes, then publish. A listing stays draft until you publish it, so nothing is ever visible before its audio has landed.

# 1. Create the draft. Nothing is public yet.
BEAT=$(curl -s -X POST https://kiddynaconnect.com/api/v1/beats \
  -H "X-API-Key: $KONNECT_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{"title":"Midnight Drive","bpm":140,"key":"F#m",
       "tags":["dark","trap"],
       "licenses":[{"tier":"MP3 Lease","price_cents":2999}]}')
ID=$(echo "$BEAT" | grep -o '"id":[0-9]*' | head -1 | cut -d: -f2)

# 2. Ask where the audio goes. You get back a URL to PUT to -- a presigned
#    bucket URL if storage is configured, otherwise this app. Same client code.
URL=$(curl -s -X POST https://kiddynaconnect.com/api/v1/beats/$ID/audio \
  -H "X-API-Key: $KONNECT_KEY" \
  -H "Content-Type: application/json" \
  -d '{"filename":"midnight-drive.wav"}' \
  | grep -o '"url":"[^"]*"' | cut -d'"' -f4)

# 3. Send the bytes. Raw body, no multipart.
curl -s -X PUT "$URL" \
  -H "X-API-Key: $KONNECT_KEY" \
  --upload-file ./midnight-drive.wav

# 4. Go live. This is the only step that makes it visible on your store.
curl -s -X PATCH https://kiddynaconnect.com/api/v1/beats/$ID \
  -H "X-API-Key: $KONNECT_KEY" \
  -H "Content-Type: application/json" \
  -d '{"visibility":"public"}'

Endpoints

PathScope
POST/api/v1/beatsbeats:writeCreate a draft listing (metadata only).
POST/api/v1/beats/{id}/audiobeats:writeAsk where to send the audio. Returns a URL to PUT the file to.
PUT/api/v1/beats/{id}/audiobeats:writeThe fallback upload target when object storage is not configured.
PATCH/api/v1/beats/{id}beats:writeUpdate metadata, set prices, or publish.
GET/api/v1/beatsbeats:readList your beats, newest first. Cursor paginated.
GET/api/v1/beats/{id}beats:readFetch one beat.
DELETE/api/v1/beats/{id}beats:writeDelete a beat and its audio.

Prices are integer cents

A licence price is a whole number of cents — 2999, not 29.99. Sending a fractional value is rejected rather than rounded, because silently turning 29.99 into 29.989999999999998 is how storefronts end up a cent short.

Visibility

One of draft, unlisted, public. Only public appears on your store page; unlisted is reachable by direct link.

Idempotency

Send an Idempotency-Key header on POST and a retry returns the original response instead of creating a second listing. Keys are remembered for 24 hours. Use one per logical upload — a fresh UUID per beat, reused across that beat's retries.

Pagination

Lists are cursor-based. Pass limit (max 100) and after, and read the next cursor from the response envelope. Cursors are stable as your catalogue changes; offsets are not, which is why there is no page parameter.

GET /api/v1/beats?limit=50&after=1042

Rate limits

120 requests per 60 seconds, per key — so one script hitting its ceiling does not stop your others. Every response carries your remaining budget, not just the rejections:

X-RateLimit-Limit: 120
X-RateLimit-Remaining: 118
X-RateLimit-Reset: 1786125810

Over the limit you get 429 with the usual error envelope and a Retry-After header in seconds. Rejected requests are not counted against you, so backing off actually clears the window.

Errors

Every error uses one envelope. code is stable and safe to branch on; message is for humans and may change.

{
  "error": {
    "code": "invalid_request",
    "message": "title is required.",
    "field": "title"
  }
}
StatusCodeMeaning
400invalid_requestSomething in the body is wrong. `field` names which.
401unauthorizedMissing or unrecognised key.
403insufficient_scopeValid key, but it lacks the scope this route needs.
404not_foundNo such beat, or not yours.
409conflictThe request collides with the current state.
422beat_not_readyThe listing has no audio yet, so it cannot go public.
429rate_limitedOver the per-key limit. Retry after the window resets.
500server_errorOur fault. Safe to retry.

Still early. The beats endpoints are stable and versioned under /api/v1. The scrape:* scopes are reserved and not yet accepted anywhere. There are no webhooks yet — poll GET /api/v1/beats if you need upload status.