Get started

Run your first job

AudioKit works in jobs. You send a link to a file and the operation you want; AudioKit checks the file, runs the operation and sends the result to your webhook.

Before you start

  • An API key. AudioKit is in early access: request a key. Keys look like ak_….
  • A file AudioKit can download. An https URL to it, usually a signed GET URL from your storage. It must resolve to a public address, including after redirects.
  • The file's sha256. Hash the exact bytes your URL serves, for example with sha256sum master.wav.
  • Optional: a webhook endpoint. An https URL that accepts POST. Without one, you poll for the result.

Check the service

These two endpoints need no key. /v1/capabilities lists the operations the server runs right now.

shell
curl https://api-production-cd43.up.railway.app/v1/health
curl https://api-production-cd43.up.railway.app/v1/capabilities
responses, shortened
{ "status": "ok", "version": "0.1.0" }

{ "operations": [
    { "operation": "loudness", "outputs": [], "async": true, "requires_consent": false },
    { "operation": "preview", "outputs": ["audio"], "async": true, "requires_consent": false },
    …
] }

Submit a job

This measures a file's loudness. job_id is yours: use something you can look up later. It's also the idempotency key.

shell
curl https://api-production-cd43.up.railway.app/v1/jobs \
  -H "Authorization: Bearer $AUDIOKIT_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "job_id": "job-0193",
    "operation": "loudness",
    "input": {
      "url": "https://storage.example.com/master.wav?signature=abc",
      "sha256": "837d2e9c0f1a4b5c6d7e8f9012a3b4c5d6e7f8091a2b3c4d5e6f708192a3b4c5",
      "filename": "master.wav"
    },
    "webhook": { "url": "https://you.example/audiokit/webhook" }
  }'

You get 202 Accepted and the job, with status: "queued" and AudioKit's own provider_job_id. Keep that ID: it's how you fetch or cancel the job.

Other operations work the same way. Some take params; the reference lists them for each operation.

Get the result

When the job finishes, AudioKit POSTs a job.succeeded, job.failed or job.canceled event to your webhook, with the job as data. You can also fetch the job at any time:

shell
curl https://api-production-cd43.up.railway.app/v1/jobs/$PROVIDER_JOB_ID \
  -H "Authorization: Bearer $AUDIOKIT_KEY"
a succeeded loudness job
{
  "provider_job_id": "01a19981-fb9f-774e-b139-7cc6ac648659",
  "job_id": "job-0193",
  "operation": "loudness",
  "status": "succeeded",
  "result": {
    "integrated_lufs": -22.02,
    "loudness_range_lu": 0.0,
    "true_peak_dbtp": -21.33,
    "sample_peak_dbfs": -21.33,
    "sample_rate": 44100,
    "channels": 2,
    "duration_seconds": 5.0
  },
  "provenance": {
    "input_sha256": "837d2e9c0f1a4b5c6d7e8f9012a3b4c5d6e7f8091a2b3c4d5e6f708192a3b4c5",
    "audiokit_version": "0.1.0",
    "operation": "loudness",
    "params": {}
  },
  "created_at": "2026-11-01T23:46:35.039517Z",
  "started_at": "2026-11-01T23:46:35.044170Z",
  "finished_at": "2026-11-01T23:46:35.730192Z"
}

status moves from queued to running to succeeded, failed or canceled. A failed job has error: { "code", "message" } instead of result; the codes are in the reference.

Verify webhooks

Every event carries X-Signature: t=<unix seconds>,v1=<hex>, an HMAC-SHA256 of "{t}.{raw_body}" keyed with your webhook secret. Verify it against the raw body before parsing, and reject timestamps more than 300 seconds old.

Node
import crypto from "node:crypto";

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

def verify(secret: bytes, raw_body: bytes, header: str, now: float | None = None) -> bool:
    parts = dict(p.split("=", 1) for p in header.split(",") if "=" in p)
    t = int(parts.get("t", 0))
    if not t or abs((now or time.time()) - t) > 300:
        return False
    mac = hmac.new(secret, f"{t}.".encode() + raw_body, hashlib.sha256).hexdigest()
    return hmac.compare_digest(mac, parts.get("v1", ""))

Check your code against this vector. Its timestamp is fixed, so pass now as 1793440800 when testing with it.

webhook signature test vector
secret  whsec_test
body    {"event_id":"evt-1"}
header  t=1793440800,v1=27d3e5b970e7b39136241354c673e7849a3bceccff29da0b09c76a1d9b73b822

Reply with any 2xx to acknowledge. Otherwise AudioKit retries after 10 s, 1 min, 5 min, 30 min, 2 h and 6 h, then stops; the job itself always has the final state. Each event has a unique event_id: ignore ones you've already handled.

Retries and idempotency

If a submit times out, send the same request again. The same body with the same job_id returns the existing job with 200, so nothing runs twice. The same job_id with a different body is refused with 409 idempotency_conflict. To process a file again, use a new job_id.

Operations that write files

transcode and preview write a new audio file. Give AudioKit somewhere to put it: a signed PUT URL in outputs.audio, with any headers the signature needs. AudioKit sets Content-Type and Content-Length itself.

request body
{
  "job_id": "job-0194",
  "operation": "preview",
  "input": { "url": "https://storage.example.com/master.wav?signature=abc", "sha256": "837d2e9c…" },
  "params": { "duration_seconds": 30 },
  "outputs": {
    "audio": {
      "url": "https://storage.example.com/upload/preview.mp3?signature=def",
      "headers": { "x-upsert": "true" }
    }
  },
  "webhook": { "url": "https://you.example/audiokit/webhook" }
}

The result reports the uploaded file's sha256 and size_bytes. Hash what you stored and compare.

Cancel a job

POST /v1/jobs/{provider_job_id}/cancel. A queued job is canceled at once (200). A running job is asked to stop (202) and a job.canceled event follows. A finished job comes back unchanged (200).

Next