Public API Audits Guide

How to read third-party audit data from the Retraced platform via the Public API.

An audit is an assessment of one facility against one standard, carried out by an audit body: BSCI at a garment factory, SMETA at a fabric mill, GOTS at a dyehouse. It carries the report as a file, the result the auditor awarded, and the date the next one is due. The issues an audit raised become findings, which are worked through in CAPA.

Three read-only endpoints expose them:

  1. GET /api/v2/audits - the audits your company can see.
  2. GET /api/v2/audits/{id} - one audit.
  3. GET /api/v2/audits/{id}/versions - the other assessments of the same facility against the same standard.

Over the Public API, audits are read-only: every endpoint below is a GET. Audits are uploaded and changed in the Retraced platform.

Setup

# Base URL
BASE_URL="https://publicapi.retraced.com/api/v2"

# Authentication - use your company API key
API_KEY="your-company-api-key"

All examples use curl. The API key is passed via the companyapikey header, and every response uses the shared envelope described in Getting Started. What you can see is scoped to the company the key belongs to - see Who can see an audit.


The model

audit                            "amfori BSCI 2026" at Lotus Garments
  │   auditTypeId → BSCI           carried out 2026-03-18, next due 2027-03-18
  │   auditReportFile              the PDF the auditor issued
  │
  ├── finding ────────────────►  GET /capa/findings?sourceType=AUDIT&sourceId={auditId}
  │   "exit lighting broken"
  │
  └── version series ─────────►  GET /audits/{id}/versions
      the same facility + standard, year after year

The ids that join them:

From Field Joins to
Audit companyId The audited company. GET /companies/{id}
Audit auditTypeId The standard it was carried out against. See GET /audit-types
Audit uploaderCompanyId The company that put it on the platform - often a brand, not the facility
Finding sourceId + sourceType The audit behind it: sourceType is AUDIT and sourceId is the audit's id

⚠️ Related standards are separate audit types, and the id is what tells them apart. SEDEX_SMETA is "Sedex (SMETA) - 2 Pillar" and GUB825FYUYF is "Sedex (SMETA) - 4 Pillar" - two different assessments that read almost identically by name. Match on auditTypeId, never on auditTypeName, and remember each id is its own version series: a facility assessed against both has two series, not one.

facilityScore is free text, not a scale. Each audit body writes its own result in its own vocabulary, so the column holds "A", "Green", "94", "Approved" and "In Progress" side by side. It is safe to display and to compare within one auditTypeId; it is not comparable across standards and must not be parsed as a number.

Who can see an audit

Three rules, and no others:

An audit that meets none of the three returns 404 from GET /audits/{id} - the same answer as an id that does not exist, so the API never confirms that someone else's audit exists. It is also simply absent from your list rather than appearing in a redacted form.

There is no public equivalent of the dashboard's browse-the-network-and-request-access flow. Every audit in a response is one you can already read.


List audits

# Everything you can see, most recently changed first
curl "$BASE_URL/audits" -H "companyapikey: $API_KEY"

# One supplier's BSCI audits, newest assessment first
curl "$BASE_URL/audits?companyId=cmp_3Nq8Wl5T&auditTypeId=BSCI&sort=auditDate&order=desc" \
  -H "companyapikey: $API_KEY"

# Everything falling due, so you can chase renewals
curl "$BASE_URL/audits?expirationStatus=EXPIRED&latestOnly=true" \
  -H "companyapikey: $API_KEY"

Query parameters:

Parameter Type Default Notes
page number 1 1-based page number.
limit number 20 Page size, 1-100.
sort enum updatedAt updatedAt, auditDate, nextPlannedAuditDate, name, type, referenceNumber, companyName, uploaderCompanyName, facilityScore or auditedBy. Audits with no value for the chosen field always sort last, whichever direction you choose.
order enum desc asc or desc.
latestOnly true | false false true returns one audit per company and standard instead of every version - see Versions and renewals.
companyId string - The audited company.
auditTypeId string - The standard, e.g. BSCI. Exact match on the id, not the display name.
referenceNumber string - The certificate or report number the audit body issued. Exact match, case-sensitive.
visibility enum - PUBLIC or PRIVATE.
generatedBy enum - USER_GENERATED, API or AI_GENERATED.
auditDateFrom / auditDateTo date - YYYY-MM-DD, both bounds inclusive, compared in UTC. Audits with no audit date are excluded by either bound.
expirationStatus enum - EXPIRED or NON_EXPIRED, compared against nextPlannedAuditDate - see Enumerations.
isArchived true | false - Omit to get both shelves - see Archiving.
updatedAfterUnixMs / updatedBeforeUnixMs number - Unix milliseconds, both bounds inclusive.

Response (200):

{
  "metadata": {
    "success": true,
    "pagination": { "page": 1, "limit": 20, "total": 27, "totalPages": 2 }
  },
  "data": [
    {
      "id": "aud_7Rk4Dm2X",
      "companyId": "cmp_3Nq8Wl5T",
      "companyName": "Lotus Garments Ltd.",
      "auditTypeId": "BSCI",
      "auditTypeName": "amfori BSCI",
      "name": "amfori BSCI 2026",
      "type": "Full Audit",
      "referenceNumber": "BSCI-2026-118934",
      "auditedBy": "Bureau Veritas",
      "auditDate": "2026-03-18T09:00:00.000Z",
      "nextPlannedAuditDate": "2027-03-18T09:00:00.000Z",
      "facilityScore": "B",
      "visibility": "PRIVATE",
      "isArchived": false,
      "externalSource": null,
      "generatedBy": "USER_GENERATED",
      "digitizationStatus": "COMPLETED",
      "digitizationPercentage": 100,
      "uploaderCompanyId": "cmp_1Az6Yp0R",
      "uploaderCompanyName": "Northwind Apparel",
      "auditReportFile": {
        "id": "fil_8cKp2Qv1",
        "url": "https://files.retraced.co/audits/bsci-2026-lotus.pdf?signature=...",
        "contentType": "application/pdf",
        "fileType": "AUDIT_REPORT"
      },
      "correctiveActionPlanFile": null,
      "createdAt": "2026-03-20T11:42:07.000Z",
      "updatedAt": "2026-04-02T08:15:33.000Z",
      "createdByUserId": "usr_5Jd9Hs3B",
      "createdByCompanyId": "cmp_1Az6Yp0R",
      "createdByImpersonateId": null,
      "createdByApiKeyId": null,
      "createdByApiKeyLabel": null,
      "updatedByUserId": "usr_5Jd9Hs3B",
      "updatedByUserName": "Priya Raman",
      "updatedByCompanyId": "cmp_1Az6Yp0R",
      "updatedByCompanyName": "Northwind Apparel",
      "updatedByImpersonateId": null,
      "updatedByImpersonateName": null,
      "updatedByApiKeyId": null,
      "updatedByApiKeyLabel": null
    }
  ]
}

Versions and renewals

A facility audited every year against the same standard produces a series: one audit per year, all with the same companyId and auditTypeId. That pair is the series key.

This is the one place the API deliberately shows you more than the dashboard. The dashboard lists one row per series - the current audit - so a supplier audited against BSCI for the last twenty-seven years is one row on screen and twenty-seven objects here. Neither is wrong; the dashboard answers "what is this supplier's BSCI status", and the API gives you the record.

Three ways to work with that:

# Every version - the default, and what a full sync wants
curl "$BASE_URL/audits?companyId=cmp_3Nq8Wl5T" -H "companyapikey: $API_KEY"

# One per company and standard - what the dashboard shows
curl "$BASE_URL/audits?companyId=cmp_3Nq8Wl5T&latestOnly=true" -H "companyapikey: $API_KEY"

# Walk one series
curl "$BASE_URL/audits/aud_7Rk4Dm2X/versions" -H "companyapikey: $API_KEY"

Which audit is "current" is not simply the newest upload. The platform ranks a series by furthest nextPlannedAuditDate first, then latest auditDate, then most recently created. That is deliberate: uploading an older report late must not displace the audit that is actually still valid. latestOnly=true and /versions both use this order, so you never have to reproduce it.

Three things to know about /versions:

Fetch an audit

curl "$BASE_URL/audits/aud_7Rk4Dm2X" -H "companyapikey: $API_KEY"

Returns exactly the fields the list returns - there is no wider detail view. 404 when the audit does not exist or is not visible to your company; the two cases are deliberately indistinguishable.

Files

An audit carries up to two documents, each published as an object or null:

Field What it is
auditReportFile The report the audit body issued.
correctiveActionPlanFile A corrective action plan document, where one was uploaded as a file.

Each object carries four fields:

Field Notes
id Id of the file record.
url A temporary download link - see below. null when the file record has no stored object behind it.
contentType MIME type, e.g. application/pdf. null when none was recorded on upload.
fileType How the platform classifies the file, e.g. AUDIT_REPORT. null on rows that predate the column.

⚠️ correctiveActionPlanFile is a document, not the structured plan. The tracked plans, their actions and their progress live in CAPA - see GET /capa/plans. An audit can easily have both, or either, or neither.

url is a temporary download link, valid for one hour from the moment the response was generated. It grants read access to that one file and nothing else. Fetch a fresh response rather than storing the link; a stored one stops working and cannot be refreshed in place. url is null when the file record has no stored object behind it.

Archiving

Archiving takes an audit off your working list without deleting it. It is per company: an audit a partner archived is still active for you, and archiving never affects what anyone else sees. An audit your own company owns is never archived for you, because the archive shelf belongs to the sharing relationship.

⚠️ Audits differ from every other resource here. On orders, styles and CAPA, omitting isArchived gives you the active records only. On audits, omitting it gives you both shelves. Send isArchived=false explicitly if you want only the active ones.

isArchived Returns
omitted both shelves
false the audits you have not archived
true the audits your company has archived

See also Archiving vs. deleting.

Record provenance

Every audit says who created it and who last changed it. This is your own audit trail: use it to reconcile what your integration wrote against what people changed in the dashboard.

Each write records one channel. A change made in the Retraced dashboard fills the user fields and leaves the API-key ones null; a change made with an API key does the opposite. So a null here means "not that channel", not "unknown".

Field What it says
createdByUserId, createdByCompanyId Who created the audit record, and for which company.
createdByApiKeyId, createdByApiKeyLabel The key that created it, and the label that key carried at the time - kept as written even if the key was later renamed or revoked.
createdByImpersonateId Set only when Retraced support created it on your behalf.
updatedByUserId, updatedByUserName Who last changed it.
updatedByCompanyId, updatedByCompanyName Which company that person acted for. On a shared audit this can be a company other than the audited one.
updatedByApiKeyId, updatedByApiKeyLabel The key that made the last change.
updatedByImpersonateId, updatedByImpersonateName Set only when Retraced support made the last change on your behalf.

Three creator fields are absent rather than null: there is no createdByUserName, createdByCompanyName or createdByImpersonateName on an audit. The record has never stored them, and publishing a key that is permanently null would suggest the data exists. Resolve createdByCompanyId through GET /companies/{id} when you need the name.

The *Impersonate* fields are set when Retraced staff acted on your company's behalf through support. They are null on everything your own people and keys did, which is almost every audit. When they are not null, they are the most important part of the trail.

Enumerations

Where Values
visibility PUBLIC (readable by every company connected to the audited company; any other company may request access through the dashboard), PRIVATE (only companies it has been shared with). Not a statement about your own access.
generatedBy USER_GENERATED (uploaded in the dashboard), API (created through an integration), AI_GENERATED
digitizationStatus PROCESSING, COMPLETED, FAILED, or null where extraction was never run
expirationStatus (filter only) EXPIRED, NON_EXPIRED
sort updatedAt, auditDate, nextPlannedAuditDate, name, type, referenceNumber, companyName, uploaderCompanyName, facilityScore, auditedBy
latestOnly, isArchived true, false

A value outside the documented set is rejected with 400 on every one of these parameters.

expirationStatus is total. EXPIRED means nextPlannedAuditDate is before today; NON_EXPIRED means today or later. An audit due today matches NON_EXPIRED and only that - the boundary belongs to exactly one side, so scanning both values never double-counts and never drops an audit. Audits with no nextPlannedAuditDate match neither and are excluded by either value; list them by omitting the parameter.

Incremental sync

updatedAfterUnixMs and updatedBeforeUnixMs are Unix timestamps in milliseconds and inclusive, so an audit sitting exactly on your cursor comes back again - de-duplicate by id.

# Everything that changed since the last run
curl "$BASE_URL/audits?updatedAfterUnixMs=1788307200000&sort=updatedAt&order=asc&limit=100" \
  -H "companyapikey: $API_KEY"

Store the largest updatedAt you have seen and pass it on the next run. Pair it with sort=updatedAt&order=asc so a long sync pages forward through the changes in order.

Two things worth planning for:

See Pagination, sorting & incremental sync for the shared paging rules and Rate limits for the request budget.

End-to-end example

# 1. The current audit for each of your suppliers, oldest first so renewals surface
curl -s "$BASE_URL/audits?latestOnly=true&sort=nextPlannedAuditDate&order=asc&limit=100" \
  -H "companyapikey: $API_KEY" \
  | jq '.data[] | {companyName, auditTypeName, nextPlannedAuditDate, facilityScore}'

# 2. One audit's full history of assessments
AUDIT_ID=$(curl -s "$BASE_URL/audits?latestOnly=true&limit=1" \
  -H "companyapikey: $API_KEY" | jq -r '.data[0].id')
curl -s "$BASE_URL/audits/$AUDIT_ID/versions" -H "companyapikey: $API_KEY" \
  | jq '.data[] | {auditDate, facilityScore, referenceNumber}'

# 3. What that audit found, and the work raised from it
curl -s "$BASE_URL/capa/findings?sourceType=AUDIT&sourceId=$AUDIT_ID&scope=network" \
  -H "companyapikey: $API_KEY" | jq '.data[] | {title, urgency, status}'

# 4. Download the report
curl -s "$BASE_URL/audits/$AUDIT_ID" -H "companyapikey: $API_KEY" \
  | jq -r '.data.auditReportFile.url' | xargs curl -o report.pdf

Common errors

Status When How to fix
400 An enum value outside the documented set (sort, order, visibility, generatedBy, expirationStatus, latestOnly, isArchived), a malformed YYYY-MM-DD date, a non-numeric …UnixMs, page below 1, or limit outside 1-100. Check the parameter table above.
401 Missing or invalid credentials (token_or_api_key_invalid). Check the companyapikey header name (all lowercase) and the key value.
403 Authenticated, but the credential does not resolve to a company (insufficient_rights). Use a company API key; a key not bound to a company cannot read audit data.
404 The audit does not exist, or your company is neither the audited company nor one it was shared with - deliberately indistinguishable. Verify the id. Confirm the audit is yours or has been shared with you.
429 The key's per-minute allowance is exhausted. Honour Retry-After and watch X-RateLimit-Remaining - see Rate limits.

Three behaviours that are not errors and bite silently: