Integrations

The REST API is designed to be driven from an automation platform. This page covers the two we are asked about most, and the polling pattern that both of them — and any scheduled script you write yourself — need to get right.

Whichever tool you use, start by creating a dedicated API key for it on the developers settings page. One key per integration means you can revoke or rotate it without disturbing anything else, and it gives that integration its own rate-limit bucket.

Make.com

Make can build an app directly from our OpenAPI specification, so you do not have to define a single module by hand.

  1. Open Custom apps and create a new app.

  2. Choose to import an OpenAPI 3 definition and give it this URL:

    https://audioaudit.io/api/rest/v1/openapi.json
    
  3. Set the connection type to API Key Auth, sending the key in a header:

    FieldValue
    Parameter typeHeader
    NameAuthorization
    ValueBearer YOUR_API_KEY
  4. Test the connection against GET /me. A 200 with a workspace object means the key works and tells you which workspace it is bound to. A 401 means the key is wrong, disabled, or belongs to an Organisation with no owner.

The spec carries every endpoint, parameter, response schema and error code, so the imported modules arrive already typed and already documented. When we add an endpoint or a field, re-importing the spec picks it up — v1 only ever changes additively, so a re-import cannot break a scenario you have already built.

For a scheduled scenario, use the polling recipe below rather than Make's generic "watch records" behaviour.

Zapier

Zapier has no OpenAPI import, so a private integration is defined by hand in the Platform UI or CLI. It is a small amount of work: one authentication block and one trigger.

Authentication

Choose API Key authentication with a single input field named api_key, then add a request header:

HeaderValue
AuthorizationBearer {{bundle.authData.api_key}}

For the connection test, use GET https://audioaudit.io/api/rest/v1/me. It performs no billing lookup, so it is fast and cannot fail because of an unrelated outage — and its response names the workspace, which makes it a genuinely useful connection label.

A polling trigger for finished reports

The trigger that pays for itself is "a report finished" — it fires when analysis completes, so a Zap can post the score to Slack, write a row to a sheet, or open a ticket when a check fails.

Poll this URL:

GET https://audioaudit.io/api/rest/v1/reports?status=COMPLETE&updated_since=<watermark>&limit=100

Four things about that URL are load-bearing.

status=COMPLETE, in upper case. The filter speaks the same vocabulary the response does. complete is not a value the API accepts and will come back as a 400 validation_error — deliberately, so that a typo is an error you see rather than an empty list you mistake for "nothing new".

updated_since, never created_since. This is the single most important line on this page. A report is created when it is queued and only becomes COMPLETE through a later update, and reports do not finish in the order they were created — a five-minute episode queued at 09:01 finishes long before a two-hour one queued at 09:00. A poller keyed on created_at that has already seen the short report has moved its watermark past the long one, and will never be given it. The report simply vanishes from the integration, silently, with nothing in any log to say so. updated_at is the only column that moves when a report reaches its final state, so it is the only one a watermark can safely follow.

Take the watermark as max(updated_at) across everything you read — not from the first item. The listing is ordered by creation time, newest first, which is not the column you are filtering on. So items[0] is the most recently created report in the page, not the most recently updated one, and a watermark taken from it is lower than the highest value you were actually handed. The consequence is not a lost report but a stuck one: every subsequent poll re-delivers the same rows, forever, if the offending row never reaches the top of the listing. Read the whole page, then:

const watermark = items.reduce(
  (highest, item) => (item.updated_at > highest ? item.updated_at : highest),
  previousWatermark
)

Every updated_at this API returns is ISO-8601 in the same fixed +00:00 form, and timestamps in one identical format compare correctly as plain strings — so comparing them against each other works without parsing dates.

The one string that is not in that format is your seed, and mixing formats breaks the comparison. "2026-08-06T06:00:00.500000+00:00" > "2026-08-06T06:00:00Z" is false, because . sorts below Z, so a Z-form seed can swallow the values it is compared against and leave the watermark where it started — the same rows redelivered on every cycle. Seed with a value in the form the API returns, or parse both sides into real dates before comparing.

If you cannot finish paging, keep the watermark you started with. When has_more is true and you stop early — a timeout, a platform step limit, an error on page three — do not store a partial maximum. Advancing the watermark to the highest value from the pages you did read discards everything on the pages you did not. Keeping the old watermark costs you one duplicate poll; advancing it part way costs you reports. Because updated_since has already narrowed the listing to what changed, paging to the end is cheap in the normal case: after the first run there is usually one page or none.

Deduplication

Zapier deduplicates on the id field, which every report carries and which never changes. You do not need to add anything for this. A report that is updated again after completing would be delivered once and then suppressed, which is the behaviour you want.

First run

Do not start with an empty watermark — the first poll would deliver every report your workspace has ever completed. Seed it with the current time or a recent date, and let the trigger fill in from there.

Write the seed in the form the API returns — 2026-08-06T06:00:00+00:00, offset spelled out — rather than the Z form. updated_since accepts either, and recovers a + the query string ate, but the string comparison above does not: a Z seed sorts above the timestamps meant to replace it. The safest seed of all is an updated_at read out of a response, which is in the right form by construction.

General polling guidance

These apply to any scheduled integration, hand-written or not.

  • Poll no faster than you need. Reports take minutes, not seconds. Every five to fifteen minutes is plenty for a completion trigger, and the API allows 100 requests per minute per key, which a sensible poller never approaches.
  • Filter server-side. status, podcast_id and updated_since narrow the listing before it is paged, so a well-filtered poll reads one small page. Fetching everything and filtering in your own code wastes your rate-limit budget on rows you throw away, and can never be complete anyway — you only ever see the first limit rows.
  • Honour Retry-After on a 429. It carries the exact number of seconds until the window resets, not an estimate. Sleep for it and retry rather than backing off blindly.
  • Retry on 5xx, not on 4xx. A 500 is worth retrying. A 400, 401, 403, 404 or 409 means the request itself is wrong and will keep being wrong — retrying it just spends your budget. In particular a 409 on POST /reports means that upload has already produced a report; mint a fresh id rather than repeating the call.
  • Poll the list, not each report. Once a report has been created, waiting for it via GET /reports?updated_since=… costs one request for all outstanding reports. Polling GET /reports/{report_id} in a loop per report costs one request each, and gets expensive quickly.
  • Keep GET /credits out of the loop. It is the one endpoint that consults the billing provider. Read it when you want to show a balance, not on every cycle.
  • Ignore fields you do not recognise. New response fields can appear on v1 without notice. Configure the platform's JSON handling — or your deserialiser — to skip unknown keys rather than fail on them.

Something else?

If you are building against a different platform, or the shape you need is not here, email info@audioaudit.io and tell us what you are wiring up. Packaged Zapier and Make apps, and webhooks so you can stop polling altogether, are both on the roadmap — hearing what you need moves them up it.