GraphQL API

The GraphQL API is the supported way to integrate with Audio Audit programmatically. It is what our API customers use today, and it covers listing your podcasts, listing reports, and reading a finished report.

Endpoint

POST https://audioaudit.io/api/graphql

Send a JSON body containing a query string and, optionally, a variables object. Authenticate with an API key in the Authorization header — see Authentication for how to create one.

Authorization: Bearer your-api-key-here
Content-Type: application/json

GraphQL reports problems in an errors array in the response body, so check for that rather than relying on the HTTP status code alone.

Exploring the Schema

GraphiQL is available at /api/graphql. This web UI allows you to explore the schema. In the top-right corner is a link to the auto-generated documentation. You can also start typing a query in the editor using Ctrl+Space to trigger auto-complete and Ctrl+Enter to execute.

If you are logged in, in another tab, then your current session cookies will be used automatically — so GraphiQL is for exploring, and API keys are for the integrations you build. Please read "What We Support" at the end of this page before building on anything you find in GraphiQL that is not documented here.

/schema.graphql is the complete SDL for the live API — every type, field, argument and mutation, with descriptions. It is generated from the running schema and checked on every pull request, so it tracks the API closely. That makes it accurate, not stable: outside the operations documented on this page, it follows the schema's changes rather than shielding you from them.

It is also the file to hand to an LLM or a code generation tool when you want one to write queries against Audio Audit for you.

Workspaces

An API key belongs to the workspace it was created in — either your personal workspace or one Organisation. A key created against an Organisation acts as that Organisation's owner account.

The podcasts and reports queries take an optional organisationId. Pass it to get that Organisation's shows and reports; leave it out and you get the personal workspace of the account the key acts as. If your key belongs to an Organisation, always pass organisationId.

You can look up the IDs of the Organisations available to your key:

curl -X POST https://audioaudit.io/api/graphql \
  -H "Authorization: Bearer your-api-key-here" \
  -H "Content-Type: application/json" \
  -d '{
    "query": "query { organisations { id name } }"
  }'

Listing Your Podcasts

podcasts returns the shows in the workspace, ordered by title. limit defaults to 20.

curl -X POST https://audioaudit.io/api/graphql \
  -H "Authorization: Bearer your-api-key-here" \
  -H "Content-Type: application/json" \
  -d '{
    "query": "query Podcasts($limit: Int, $organisationId: UUID) { podcasts(limit: $limit, organisationId: $organisationId) { id title feedUrl lastCheck coverImageUrl } }",
    "variables": {"limit": 20, "organisationId": null}
  }'
{
  "data": {
    "podcasts": [
      {
        "id": "816ee4c4-c4c5-4078-9dfa-865618b45200",
        "title": "Example Podcast",
        "feedUrl": "https://example.com/feed.xml",
        "lastCheck": "2026-08-05T09:12:44.318472+00:00",
        "coverImageUrl": "https://example.com/artwork.jpg"
      }
    ]
  }
}

Keep the id — it is what you match report rows against in the next query.

Listing Your Reports

reports returns the reports in the workspace, newest first. limit defaults to 10. Select podcast { id title } on each row so you can tell which show a report belongs to.

curl -X POST https://audioaudit.io/api/graphql \
  -H "Authorization: Bearer your-api-key-here" \
  -H "Content-Type: application/json" \
  -d '{
    "query": "query Reports($organisationId: UUID, $limit: Int) { reports(organisationId: $organisationId, limit: $limit) { id name status createdAt stats podcast { id title } } }",
    "variables": {"organisationId": null, "limit": 10}
  }'

As with podcasts, pass your organisationId if your key belongs to an Organisation, and leave it out for a personal key.

There is no per-show, status or date filter on this query today, and no pagination — reports takes limit but no offset, so there is no second page to fall back on. Raise limit high enough to cover all the shows you track, then narrow the rows down on your side by matching podcast.id against the show you care about. There is no maximum, but ask for what you need rather than everything.

If per-show or status filtering would help you, email info@audioaudit.io and tell us the shape you need — we are working on it and would rather build what integrators actually use.

One argument to avoid. reports also accepts a podcastId, and it is not part of the supported surface. Podcast records are shared between customers by feed URL, so for a widely followed public feed that argument can return reports belonging to other Audio Audit customers — and it is ignored entirely when organisationId is also supplied, which silently gives you the whole workspace when you asked for one show. Filter by podcast.id on your side instead.

Fetching a Report

report takes the id of a single report.

curl -X POST https://audioaudit.io/api/graphql \
  -H "Authorization: Bearer your-api-key-here" \
  -H "Content-Type: application/json" \
  -d '{
    "query": "query Report($id: UUID) { report(id: $id) { id name status createdAt stats fileInfo metadata results transcript audioUrl } }",
    "variables": {"id": "73d6c51c-cfdf-4a6d-903a-cd7e67b4382f"}
  }'

Ask only for the fields you need: results and transcript can be large.

Report IDs work like share links — anyone holding one can fetch that report, without an API key. That is deliberate, so you can share a result with someone outside your workspace, but it means you should treat report IDs as you would any other secret and avoid putting them somewhere public.

JSON-Encoded Fields

stats, fileInfo, metadata, results, transcript and coverImage are returned as JSON-encoded strings, not as GraphQL objects. Parse them a second time on your side:

{
  "data": {
    "report": {
      "id": "73d6c51c-cfdf-4a6d-903a-cd7e67b4382f",
      "name": "Episode 42: The One About Loudness",
      "status": "COMPLETE",
      "createdAt": "2026-08-05T09:14:02.771903+00:00",
      "stats": "{\"passes\": 21, \"failures\": 3, \"total\": 24, \"percent\": 87, \"time_remaining\": 0}"
    }
  }
}

stats is the headline summary: how many checks passed, how many failed, the total number of checks, and percent — the score shown in the Audio Audit web app. results is the check-by-check detail behind it, each entry carrying a name and verbose_name, a status of PASS or FAIL, the measured value, its measurement_unit, and a description.

Waiting for a Report to Finish

There are no webhooks or callbacks yet, so poll. A report's status is one of:

  • PENDING — accepted, not started
  • SCHEDULED — queued for a worker
  • IN_PROGRESS — being analysed
  • COMPLETE — finished, results are available
  • FAILED — analysis did not complete
  • OUT_OF_QUOTA — not analysed, because the workspace was out of credits

The first three are transient; the last three are final. The pattern:

  1. Call reports for your workspace, selecting id status podcast { id }, and pick out the newest row whose podcast.id is the show you care about. Rows come back newest first, so that is the first match rather than the first row. If nothing matches, raise limit and ask again — there is no offset to page with.
  2. If its status is COMPLETE, you already have what you need — stop.
  3. Otherwise call report with that row's id every 30 seconds or so, asking only for id status stats, until the status becomes COMPLETE, FAILED or OUT_OF_QUOTA.

Hold on to report ids you have already seen, so that a later run can tell a genuinely new report from one you have processed.

While a report is IN_PROGRESS, the time_remaining value inside stats is our estimate of the milliseconds left, which is a good basis for how long to wait between polls. It is 0 in every other state, and before we have measured the audio. It goes negative when a report is taking longer than we estimated — treat that as "still working", not as a failure.

A report for a one-hour episode typically takes around five minutes, so poll patiently rather than tightly, and give up after a generous ceiling rather than looping forever.

What We Support

The operations documented on this page and on Authenticationuser, organisations, podcasts, reports and report, with the arguments and fields described above — are stable. We will not rename or remove a documented field, argument or enum value without telling you first. The one exception is called out explicitly above: reports(podcastId:) is not supported — it can return other customers' data, and it may change or disappear.

These operations evolve additively. New fields, new optional arguments and new status values can appear at any time. Build your integration so that fields you do not recognise are ignored, and so that an unfamiliar status value does not break it.

The schema is fully introspectable through GraphiQL, and it contains a great deal more than this page describes. Everything not documented here is internal. It exists to serve the Audio Audit web app, it is not part of the API contract, and it may change or disappear without notice. If you need something that is only reachable that way, email info@audioaudit.io and tell us what you are building — we would rather document it than have you depend on it quietly.

A REST API is in development. It will sit alongside the GraphQL API rather than replace it.