snvrkotics

Developers

Your rights data, over HTTP.

5 read endpoints over the catalog, releases, links, royalties and the ledger, plus 4 signed webhook events so an integration is told rather than having to ask. On Pro and above.

Read-only, deliberately. Writing rights records over an API is how a catalog gets quietly corrupted by a script nobody is watching; every change still happens in the product, where it is validated and attributed.

Getting a key

One header, and you are in

Make a key in Settings → API. It is shown once and stored only as a hash, so a copy of our database cannot call the API as you. Send it as a bearer token.

curl https://snvrkotics.com/api/v1/recordings \
  -H "Authorization: Bearer snvrkotics_sk_…"

Every response is scoped to that key’s workspace. 120 requests a minute per key, with X-RateLimit-Remaining on every response and a 429 when you pass it. A key stops working the moment its workspace drops below Pro, so entitlement is checked on each call rather than at creation.

Endpoints

5 read endpoints

GET/api/v1/recordings

Recordings in the workspace, newest first, with their identifiers and registration score.

  • isrcReturn only the recording with this ISRC.
  • limit1–200, default 50.
{
  "data": [
    {
      "id": "…",
      "title": "Night Bus",
      "version": null,
      "artist": "Mara Vance",
      "isrc": "QZSNV2600001",
      "iswc": "T-000.000.001-0",
      "durationSeconds": 214,
      "releaseDate": "2026-01-16",
      "explicit": false,
      "oneStop": true,
      "registered": true,
      "completeness": 94
    }
  ],
  "count": 1
}

GET/api/v1/releases

Releases with their UPC, label, date and track order.

  • limit1–200, default 50.
{
  "data": [
    {
      "id": "…",
      "title": "Night Bus",
      "artist": "Mara Vance",
      "type": "ep",
      "upc": "197644000015",
      "releaseDate": "2026-05-22",
      "label": "Vance Songs",
      "tracks": [{ "trackNumber": 1, "title": "Night Bus", "isrc": "QZSNV2600001" }]
    }
  ],
  "count": 1
}

GET/api/v1/royalties/summary

Net royalties by month, store and recording, from every imported statement.

  • yearLimit to one calendar year, e.g. 2026.
{
  "currency": "USD",
  "totalNetCents": 165788,
  "unmatchedCents": 2026,
  "byMonth": [{ "month": "2026-08", "netCents": 39974 }],
  "byStore": [{ "store": "Spotify", "netCents": 78207, "units": 231044 }],
  "byRecording": [{ "title": "Night Bus", "isrc": "QZSNV2600001", "netCents": 81800 }]
}

GET/api/v1/ledger/summary

The year's profit and loss: income, costs, net and the tax set-aside.

  • yearDefaults to the current calendar year.
{
  "year": 2026,
  "incomeCents": 435788,
  "costCents": 263307,
  "netCents": 172481,
  "taxSetAsideCents": 51744,
  "byMonth": [{ "month": "2026-08", "incomeCents": 39974, "costCents": 0, "netCents": 39974 }]
}

Webhooks

Told, not asked

Add an endpoint in Settings → API, pick the events, and we post to it. Every delivery is signed, and the signing secret is shown once.

statement.imported

A royalty statement finished importing.

{ "id": "…", "source": "DistroKid", "lineCount": 603, "netCents": 165788, "periodStart": "2026-01-01", "periodEnd": "2026-06-30" }

payout.created

A payout run was created for collaborators.

{ "id": "…", "totalCents": 42000, "status": "draft", "periodStart": "2026-01-01", "periodEnd": "2026-06-30" }

link.published

An nvr.to link went live.

{ "id": "…", "slug": "night-bus", "title": "Night Bus", "url": "https://nvr.to/night-bus" }

split.accepted

A collaborator accepted their share of a split sheet.

{ "shareId": "…", "recordingId": "…", "collaborator": "Deon Park", "everyPartyAccepted": false }

Verifying a delivery

Each request carries X-SNVRK-Signature: t=<unix seconds>,v1=<hex>. The signature is an HMAC-SHA256 over "<t>.<raw body>" using your signing secret. Compare in constant time, and reject anything older than five minutes so a captured delivery cannot be replayed.

import { createHmac, timingSafeEqual } from "node:crypto";

function verify(secret, rawBody, header) {
  const parts = Object.fromEntries(header.split(",").map((p) => p.split("=")));
  const t = Number(parts.t);
  if (!Number.isFinite(t) || Math.abs(Date.now() / 1000 - t) > 300) return false;
  const expected = createHmac("sha256", secret).update(`${t}.${rawBody}`).digest("hex");
  const a = Buffer.from(expected);
  const b = Buffer.from(parts.v1 ?? "");
  return a.length === b.length && timingSafeEqual(a, b);
}

Delivery, stated plainly

One attempt, with a five-second timeout, and the outcome is shown against the endpoint in settings. There is no retry queue, and we would rather say so than imply one: if a delivery matters, read the matching endpoint above to reconcile. Deliveries never block the thing that caused them, so an endpoint being down cannot fail an import.

Building something with it? Tell us — and the MCP server exposes SNVRKOTICS itself to an AI assistant with no key at all.

API and webhooks | SNVRKOTICS