Track Anatomy API

Analyze tracks, poll jobs, read reports and receive signed webhooks from your own app.

Download OpenAPI

Overview

The Track Anatomy API creates analyses from an audio file or a public YouTube link and returns the same report shown on the website. All responses are JSON. The base URL is https://trackanatomy.com/api/v1.

Create and manage keys on the API keys page. Send a key in the Authorization header:

Authorization: Bearer ta_live_...

Keys need an active credit plan. A key is shown once when it is created, can be revoked at any time and carries its own request, concurrency and daily-spend limits. Never put a key in a query string.

Credits work as they do on the website. An upload costs 10 credits and a link analysis costs 15. Each analysis includes one lyrics set; further sets for the same analysis cost 3 credits. The account daily limit of 120 credits applies across the website and the API.

An analysis response includes lyrics.includedLeft and lyrics.extraPrice. A lyrics response includes credits.included; together they show whether a set is included and what another set will cost.

Quick start

  1. Create an API key on the API keys page and keep the full key in a server-side secret store.
  2. Create an analysis with a file or a public link.
  3. Poll the analysis by id until status is succeeded or failed.
  4. Read report from the completed analysis, or use a signed webhook instead of polling.

Create from a file

curl -X POST https://trackanatomy.com/api/v1/analyses \
  -H "Authorization: Bearer $TRACK_ANATOMY_KEY" \
  -H "Idempotency-Key: upload-2026-10-30-001" \
  -F "file=@track.mp3" \
  -F "webhook_url=https://example.com/hooks/track-anatomy"
curl -X POST https://trackanatomy.com/api/v1/analyses \
  -H "Authorization: Bearer $TRACK_ANATOMY_KEY" \
  -H "Content-Type: application/json" \
  -d '{"link":"https://www.youtube.com/watch?v=abcdefghijk"}'

Poll

curl https://trackanatomy.com/api/v1/analyses/an_example \
  -H "Authorization: Bearer $TRACK_ANATOMY_KEY"

A new job answers 202. Poll no more often than every few seconds. A cached link report can answer 200 with the report already complete. Poll until status is succeeded or failed; then read report or error.

Endpoints

MethodPathPurposeCommon statuses
POST/analysesCreate an upload or link analysis.202, 200, 400, 402, 413, 422, 429
GET/analyses/{id}Read status and the report when complete.200, 401, 404
GET/analysesList analyses, newest first.200, 400
DELETE/analyses/{id}Remove a finished analysis from history.204, 400, 404
POST/analyses/{id}/lyricsWrite a lyrics set for a completed analysis.202, 400, 402, 404, 429
GET/lyrics/{id}Read a lyrics job and its result.200, 404
GET/analyses/{id}/lyricsList completed lyrics sets for an analysis.200, 404
GET/accountRead plan, credits and account usage.200, 401

Create an analysis

POST /analyses

Send either multipart/form-data with a file field, or JSON with a link field. Public YouTube videos are accepted. The first 10 minutes of a link are used.

API keys can add webhook_url. It must be an HTTPS URL on port 443, without credentials. Private, loopback, link-local, multicast and carrier-grade NAT addresses cannot be targets.

Add Idempotency-Key to make retries safe. A fresh job answers 202. A cached link report can answer 200 with the report already included.

Read an analysis

GET /analyses/{id}

The response is an Analysis object. While a job is queued or running, report is null. When it completes, report is a report object. On failure, error carries a stable code and a plain message.

List analyses

GET /analyses?limit=20&cursor=...&type=upload&q=...

limit is 1 to 100. type is upload or link. q searches the caller's analyses. Follow nextCursor until it is null.

Delete an analysis

DELETE /analyses/{id}

Deletes a finished analysis and its lyrics sets from history. It returns 204. A queued or running analysis cannot be deleted.

Create lyrics

POST /analyses/{id}/lyrics

The JSON body can contain genre, theme, language, style and, for API keys, webhook_url. Each string is optional and trimmed before use. The job answers 202; poll GET /lyrics/{id} or wait for a webhook.

Read lyrics

GET /lyrics/{id} returns a Lyrics object. GET /analyses/{id}/lyrics returns { "data": [...] } with every completed set for that analysis, newest first.

Read the account

GET /account returns the key owner's email, active plan, next plan, available credits, usage today and free-test count. API-key usage consumes the same account credits and obeys the account daily limit.

Objects

Analysis

FieldMeaning
idAnalysis id used with every later request.
objectAlways analysis.
typeupload or link.
statusqueued, running, succeeded or failed.
stageThe current progress stage, or null after finishing.
createdAt, startedAt, finishedAtUTC timestamps in ISO 8601 form.
inputFile name, size and duration for uploads; link, video id and title for links.
creditsPrice, whether a free test was used, and whether the hold was charged.
errornull, or { code, message } when the analysis failed.
reportnull until success, then a full ta.report.v1 object.
lyricsincludedLeft and extraPrice for lyrics sets.

An AnalysisSummary has the same base fields plus title, tempo and key. List responses use this smaller shape.

Lyrics

FieldMeaning
idLyrics job id.
objectAlways lyrics.
analysisIdThe analysis this set was written for.
status, stageCurrent job state and progress stage.
createdAt, finishedAtUTC timestamps in ISO 8601 form.
optionsRequested genre, theme and language.
styleThe style text used to shape the set.
creditsprice, charged and included.
errornull, or { code, message } after a failure.
lyricsnull until success, then { text, tags }.

Account

FieldMeaning
email, emailVerifiedThe account address and whether it is verified.
plan, nextPlanThe active plan period and the next queued period, or null.
credits.balanceAll remaining credits, including credits held by running jobs.
credits.availableCredits available to start another job.
credits.usedTodayCredits charged today, in UTC.
credits.dailyCapThe account's daily credit limit.
freeTests.uploadsWebsite free uploads left. API keys never use free tests.
prices.upload, prices.linkCredits for an upload and for a link analysis.
studioThe Track Anatomy Studio plan, the next and the last one ended, the free trial and the browsers signed in; used by the studio.
studio.plan, studio.nextPlanThe active Studio plan and the next stacked plan, or null.
studio.endedThe last ended Studio plan while none is active, or null.
studio.trialWhether the free trial is available and its length in days.
studio.devicesStudio browsers used and the account max.

Report

Every report has schema: "ta.report.v1".

FieldMeaning
id, analysisIdReport and analysis ids.
inputInput type, title, duration, analyzed window and optional link details.
createdAtWhen the report was created, in UTC.
core.tempobpm, feel, certainty, optional confidence and note.
core.meterMeter value, certainty, optional confidence and note.
core.grooveGroove value, certainty, optional confidence and note.
core.keyKey name, certainty, optional confidence, note and alternative key.
core.chordsChord progression, Roman numerals, certainty, note and timeline.
core.loudnessIntegrated loudness, range, dynamic range, certainty and description.
sectionsSection name, start time and short notes.
genrePrimary genre, secondary genres and a one-sentence summary.
descriptorsMood, rhythm, harmony, genre, instrument, drums and bass, vocal, arrangement, mix and production descriptor lists.
vocalsLead style and language.
notesIdentity, harmony, rhythm, topline, sound and dynamics paragraphs.
takeawaysPractical ideas for a new track in this style.
notCertainDetails the report could not determine clearly.
stylePrompts.fullFull style prompt text and its character count.
stylePrompts.coreShort measured style prompt for uploads; absent for links.

certainty is one of:

ValueMeaning
measuredMeasured from the file.
estimatedEstimated from the link.
not_certainNo clear answer.

Webhooks

An API key can attach webhook_url when it creates an analysis or lyrics set. Track Anatomy sends a signed POST request after the job succeeds or fails.

EventMeaning
analysis.finishedAn upload or link analysis succeeded or failed.
lyrics.finishedA lyrics job succeeded or failed.

The body is the same object returned by the matching GET endpoint, plus an event field. Read the job status before reading report or lyrics; a failed job includes error.

Every request has these headers:

HeaderValue
Content-Typeapplication/json
User-AgentTrackAnatomy-Webhooks/1
TA-Signaturet=<unix seconds>,v1=<HMAC-SHA256 hex>

The signed content is <timestamp>.<raw body> using the webhook secret shown when the key was created. Compare the signature in constant time and reject timestamps outside five minutes.

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

export function verify(rawBody, signatureHeader, secret) {
  const match = /^t=(\d+),v1=([0-9a-f]{64})$/.exec(signatureHeader ?? '');
  if (!match) return false;

  const timestamp = Number(match[1]);
  if (Math.abs(Math.floor(Date.now() / 1000) - timestamp) > 300) return false;

  const expected = createHmac('sha256', secret)
    .update(`${timestamp}.${rawBody}`)
    .digest();
  const received = Buffer.from(match[2], 'hex');
  return received.length === expected.length && timingSafeEqual(received, expected);
}

Answer with any 2xx status within 10 seconds. Redirects are not followed and count as failures. Failed requests retry after 1 minute, 5 minutes, 30 minutes, 2 hours and 6 hours. After the sixth failed attempt the delivery stops.

The URL must use HTTPS on port 443 and must not contain credentials. A hostname that resolves to a private, loopback, link-local, carrier-grade NAT or multicast address is refused: nothing is sent and the delivery is not retried.

Errors

Every error has the same JSON shape:

{
  "error": {
    "code": "validation_error",
    "message": "Some details are missing or not valid."
  }
}
CodeStatusMeaning
unauthorized401The key is missing, invalid or revoked.
forbidden403The caller cannot use this resource.
not_found404The resource does not exist for this account.
validation_error400A field is missing or invalid.
unsupported_format415The file type is not supported.
file_too_large413The upload exceeds the configured size limit.
too_long422The track is longer than the configured limit.
too_short422The track is too short to analyze.
invalid_link422The value is not a supported public YouTube link.
link_not_public422The video is private or unlisted.
link_unavailable422The video is removed or restricted.
email_not_verified403The account must verify its email first.
plan_required402The account needs an active credit plan.
insufficient_credits402The account does not have enough credits.
daily_limit_reached429The account or key has reached its daily limit.
too_many_running429The key or account has reached its concurrency limit.
rate_limited429Requests are arriving too quickly.
idempotency_conflict409The key was already used for a different body.
busy503The service is busy. Nothing was charged.
analysis_failed500The analysis failed. Nothing was charged.

A 429 response includes Retry-After in seconds. Do not retry other 4xx responses without changing the request.

Limits and idempotency

Each API key has three limits:

  • Requests per minute. A 429 response includes Retry-After.
  • Concurrent queued and running jobs.
  • An optional daily credit spend limit set by the customer.

The account daily limit of 120 credits also applies. Uploads and links use the same credit balance as the website, and all limits reset on UTC boundaries.

Send Idempotency-Key on every create request that may be retried. A key is stored for 24 hours.

RetryResult
Same key and same requestThe original job and response, with Idempotent-Replayed: true
Same key and different request409 idempotency_conflict
No keyA new job every time

For multipart uploads, the file content and field values are compared. Keep request field values identical when retrying. Do not reuse a key for a different analysis or lyrics request.

Changelog

v1 - initial release

  • File and public-link analyses
  • Reports, lyrics and account usage
  • Signed webhooks with retries
  • Per-key request, concurrency and daily-spend limits
  • Idempotent create requests