Settings
Audit Partner Integration
Reference for auditors integrating with Sprinto's Audit Partner API — core concepts, authentication, endpoints, and sync guidance.
Sprinto's Audit Partner API lets your integration pull the audits your client has shared with you, review controls, request missing information, and keep your own systems in sync — without a human logging into Sprinto for every step.
This page is for engineers integrating against Sprinto on behalf of an auditor user. It covers the concepts you need before reading the OpenAPI spec, what your API key can and can't do, and the operational details (pagination, sync, errors) you'll hit in practice.
Core concepts
Sprinto's audit workflow is built from a small number of entities. Skim this before your first call — the field and endpoint names below all map directly onto these.
- Auditor user — the individual auditor identity added to an audit. Your API key resolves to one auditor user, and access grants always target that specific identity. If a colleague also needs access, the client adds them as their own separate auditor user.
- Audit — one audit engagement (e.g. "SOC 2 Type II — FY2026"). Your key only ever sees audits your auditor user has been explicitly shared on.
- Control — the underlying compliance requirement being assessed (e.g. "MFA enforced on production access").
- Audit Item — a control or requirement as it's being reviewed within a specific audit. This is what you actually work with day to day — one row per item under review in that audit, each carrying its own review status.
auditItemPkis the ID you'll use across evidence, evidence-review, findings and conversation calls.- Each item carries
entityModelName, which determines what can be attached to it.OrgEvidenceRequirementitems (evidence-request-list audits) carry requested evidence, mapped policies, manual uploads and checks.Controlitems carry manual uploads and checks only. Read the field rather than assuming a shape — the two are genuinely different, not a bug.
- Each item carries
- Evidence Request — an ask, raised against a control, for the client to supply a specific piece of supporting material by a due date.
- Evidence — the supporting material itself, of four kinds:
- Manual evidence — a file a person on the client side uploaded. Manual evidence is versioned: replacing the file records a new version rather than overwriting, and the previous versions stay in the record's history. The version an audit item points at is not always the record's latest — see Evidence.
- Automated (system) evidence — a snapshot automatically captured by one of Sprinto's monitors (e.g. a passing/failing check result). This is what "control check evidence" refers to in the API.
- Policy — an approved policy or procedure document mapped to the item as evidence. Only policies with an approved, published version have a document behind them.
- Requested evidence — an evidence request mapped to the item. The request itself carries no file; the files uploaded in answer to it hang off it separately.
- Conversation — a message thread attached to a control (or a control's evidence request), for back-and-forth between you and the client without leaving an audit trail gap.
- Finding — an issue, observation or nonconformity you raise during the audit. Findings are recorded against the audit, optionally naming a specific audit item.
- Evidence sample — where a check monitors many entities, an auditor may draw a sample and test that subset rather than the population. Once drawn, the sample is fixed, and the API returns it by default.
- Access grant — each audit you can see is an explicit grant to your auditor user. Access is revocable at any time by the client, per audit; a revoked audit stops appearing (and returns "not found," not a permission error — see Errors) on your very next call. Revoking your last remaining audit deactivates your API key entirely — it stops authenticating, not just that audit disappearing.
Status and lifecycle
- Audit status:
SCHEDULED→EVIDENCE_COLLECTION→EVIDENCE_REVIEW→DUE_FOR_AUDIT→AUDIT_IN_PROGRESS→COMPLETED(orARCHIVED). - Audit Item (control) review status:
COLLECTING_EVIDENCE→INTERNAL_REVIEW→READY_FOR_REVIEW→INFO_REQUESTED→ACCEPTED.READY_FOR_REVIEWis your queue — controls the client considers done and waiting on you.INFO_REQUESTEDis a control you've flagged as needing more from the client.ACCEPTEDis a control you've formally signed off on. Setting ACCEPTED requires a finding determination — pass evidenceReviewFindingType (e.g. NO_FINDINGS) or findings text with the status, else it's rejected as "No update required."
- Evidence Request status:
UPLOAD_PENDING→REVIEW_PENDING→REUPLOAD_PENDING→UPLOADED.
Authentication
Every request carries a per-integration API token in the api-key header — there's no Authorization: Bearer scheme:
api-key: <token>Your token resolves to a dedicated Sprinto user scoped to the AUDITOR or INTERNAL_AUDITOR role. Every endpoint below checks that role, and separately checks that the specific audit/control/conversation you're asking about is one your key has been granted. The two checks are independent — a valid key with the right role still gets nothing back for an audit it hasn't been granted.
What you can do
Read: audits and controls
| Operation | Type | What it returns |
|---|---|---|
listAuditsForAuditor | Query | Every audit your key has been granted access to. Supports updatedSince for incremental sync. |
getAuditForAuditor | Query | Details of one shared audit. |
listAuditItemsForAuditor | Query | The controls under review within a shared audit, with an optional status filter (see the spec for the exact field) and updatedSince support. |
An auditPk for an audit that exists but wasn't shared with you returns the same response as one that doesn't exist at all — see Errors.
Review controls
| Operation | Type | What it does |
|---|---|---|
UPDATE_EVIDENCE_REVIEW_STATUS | Action | Moves a single control to a new review status — optionally posting a conversation message in the same call, so a status change and the reason for it land together. |
BULK_UPDATE_EVIDENCE_REVIEW_STATUS | Action | Moves a batch of controls to a new review status in one call (e.g. bulk-accepting everything that's clearly satisfied). |
Both accept evidenceReviewStatus: "ACCEPTED" — remember, include evidenceReviewFindingType when accepting; on the bulk action, items already ACCEPTED are skipped and returned in skippedAuditItemPks.
Conversations
| Operation | Type | What it does |
|---|---|---|
getConversationMessages | Query | Reads a thread's message history, including attachments as ready-to-use signed URLs. Supports updatedSince. |
SEND_CONVERSATION_MESSAGE | Action | Posts a message to a control's thread — starts one automatically if it doesn't exist yet. |
You can only read/post on a conversation whose parent control is on an audit shared with you — not any conversation in the org.
Evidence on an audit item
| Operation | Type | What it returns |
|---|---|---|
listAuditItemEvidencesForAuditor | Query | Everything attached to one audit item, in one call |
Returns manual uploads, requested evidence, mapped policies and automated/workflow checks as a single list, each row tagged with evidenceType (MANUAL, EVIDENCE_REQUESTED, POLICIES, AUTOMATED, WORKFLOW). This is the auditor's working set for an item — listAuditItemsForAuditor gives you the items, this gives you what's on them.
Evidence
| Operation | Type | What it returns |
|---|---|---|
listManualEvidences | Query | Uploaded evidence files. |
listControlCheckEvidences | Query | A catalogue of which checks have evidence attached, org-wide. Identity only — no results, no evidence rows, no link to an audit item. For what a check actually collected, use getAuditItemCheckEvidenceForAuditor. |
getManualEvidencesCount | Query | How many manual evidence files exist in the org. |
getManualEvidenceDownloadUrl | Query | A directly-downloadable, time-limited link to one manual evidence file. |
Manual evidence is read-only for an auditor key. You'll see update, archive and restore actions for manual evidence in the spec — those are customer-only and will return
403.
All four evidence queries are org-wide, not scoped to just your shared audits — as long as you have at least one active grant in the org, you can read every piece of evidence in it. This matches what auditors can already see in Sprinto's own UI; it isn't a narrower or wider carve-out specific to the API. If your only audit in an org gets revoked, this access goes with it.
If you're working a specific audit rather than sweeping the library, prefer listAuditItemEvidencesForAuditor — it's scoped to the item, tells you what type each piece of evidence is, and covers policies and checks that these four queries don't reach.
Each listManualEvidences node carries, alongside the stored columns:
mappedControls—[{ pk, number, shortText }], the controls this evidence is offered against. This answers "what is this file evidence for" without a second call per row.fileName— the uploaded file's name, ornullwhen the record is an external link rather than an upload.version— the active version number of the record.
Downloading a file. listManualEvidences returns file metadata and a raw storage key, which is not fetchable on its own. Call getManualEvidenceDownloadUrl with the evidence pk to get the bytes:
{ "downloadUrl": "https://…s3….amazonaws.com/…?X-Amz-Signature=…", "expiresInSeconds": 1800, "fileName": "access-review-q2-2026.pdf", "s3Key": "controlEvidence/…" }The downloadUrl needs no api-key header and no login session — fetch it directly. It expires after 30 minutes, so mint it when you're ready to download rather than collecting URLs up front, and re-call the query for a fresh one rather than caching.
It resolves the file on the record's current active version, so call it again after the client uploads a correction to get the replacement.
It returns data: null — not an error — in two distinct cases: the evidence is an external link rather than an upload (read evidenceFileLink from the list node instead), and the pk isn't in your org. These are deliberately indistinguishable, on the same reasoning as the 400 bucket in Errors. A file blocked by a malware scan is the one case that does error, with a 404-class response rather than a link to something quarantined.
Download is one call per evidence record; there is no batched or ZIP export.
versionis the latest available, not necessarily the version under review. When a client replaces a file, audit items set to auto-update advance to the new version only if its collection date falls inside that audit's interval and the audit isn'tCOMPLETEDorARCHIVED. A correction collected outside the window therefore leaves the audit item on the older version while this query reports the newer one — deliberately, so out-of-period evidence can't silently replace what was reviewed. If you're reconciling against a specific audit rather than sweeping the library, drive off the audit item and treat this field as "latest available".
If you need something from the client that isn't on hand, flag the control (INFO_REQUESTED) and message them via the conversation thread — the client sees it in their normal queue.
Policies
| Operation | Type | What it does |
|---|---|---|
getAuditItemPolicyDownloadUrlForAuditor | Query | A time-limited link to a policy document mapped to one of your audit items |
Take a POLICIES row's pk from listAuditItemEvidencesForAuditor and pass it with that item's auditItemPk. Like manual evidence, the link needs no api-key and no session, and expires after 30 minutes. fileType accepts PDF (default) or MARKDOWN.
Returns data: null when the policy has no approved version — a policy can be enabled and mapped to an audit item while still being an unpublished draft, which is common. Treat null as "no document yet", not an error.
Automated and workflow check evidence
| Operation | Type | What it does |
|---|---|---|
getAuditItemCheckEvidenceForAuditor | Query | The rows and headers a check collected, for one check on one of your audit items |
Pass auditItemPk plus the checkType and checkCategory from an AUTOMATED or WORKFLOW row. Returns a paginated connection of evidence rows with headers describing the columns.
The window is the audit's own evidence-collection period. An empty result means nothing was collected inside that period — not that the check never ran. A check can be passing today and return nothing here because the audit covers an earlier window. You cannot widen the window; it's derived from the audit.
Sampling
| Operation | Type | What it does |
|---|---|---|
listAuditSampleResultsForAuditor | Query | Which evidence sets were sampled on an audit, how many items, and how |
Where a check monitors many entities, an auditor may test a drawn subset instead of the population. When a sample exists, getAuditItemCheckEvidenceForAuditor returns the sample, not the population — matching what the auditor sees in the app. Pass includeFullEvidenceSet: true to read the whole population instead. The sampling block on the response tells you which you got, how the sample was drawn, and how many items it holds; it's absent when no sample was drawn.
Findings
| Operation | Type | What it does |
|---|---|---|
listAuditFindingsForAuditor | Query | The findings on one of your audits, optionally narrowed to one item |
CREATE_FINDING | Action | Raises a finding on one of your audits |
Findings are recorded against the audit, with an audit item as an optional secondary entity — so listAuditFindingsForAuditor(auditPk) returns item-level findings too. Pass auditItemPk to narrow.
Audit reports
| Operation | Type | What it does |
|---|---|---|
listAuditReportsForAuditor | Query | Report documents uploaded against one of your audits, with type and upload date |
Evidence requests
| Operation | Type | What it does |
|---|---|---|
CREATE_ORG_EVIDENCE_REQUEST | Action | Raises a new evidence request (title, assignee, due date) — the client sees it in their normal task queue. |
Creating a request only succeeds when the client org has enabled auditor collaboration with the "can request evidence" capability for you — the same setting that governs this in Sprinto's UI. Without it, the call returns 200 with a permission error in errors[]. If that's not enabled for your engagement, flag the control (INFO_REQUESTED) and message the client via the conversation thread instead. Editing, archiving, and bulk-creating requests stay admin-only.
Incremental sync
listAuditsForAuditor, listAuditItemsForAuditor, getConversationMessages and the evidence queries accept updatedSince — a full ISO 8601 datetime string:
SYNC_START=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
curl -s -X POST "{{BASE_URL}}/api/external/schema/queries/listAuditsForAuditor/execute" \
-H "api-key: $API_KEY" -H "Content-Type: application/json" \
-d "{ \"args\": { \"first\": 100, \"updatedSince\": \"$SYNC_START\" } }"The audit-item-scoped operations (listAuditItemEvidencesForAuditor, getAuditItemCheckEvidenceForAuditor, listAuditFindingsForAuditor, listAuditReportsForAuditor, listAuditSampleResultsForAuditor) do not; drive those off an item or audit you already know changed.
Only records with updatedAt >= updatedSince come back. Record a timestamp before each sync run and pass it on the next one — a bare date like "2026-06-25" is not rejected with 400 — it's silently interpreted as midnight UTC. Always send a full ISO 8601 datetime.
updatedAt tracks row-level changes only. Re-mapping a piece of evidence to a different control does not bump the evidence record's updatedAt, so a mapping-only change on the client side will never appear in an incremental pull. Dedupe by pk — the boundary is >=, so the same row can arrive twice — and schedule a periodic full resync to pick up mapping changes.
On paginated endpoints (getConversationMessages, listManualEvidences), passing updatedSince switches result ordering to oldest-first instead of the newest-first default, so a row that changes mid-pagination can't slip behind your cursor and get skipped. Ordering is therefore not comparable between calls made with and without it.
Pagination: first is capped at 100 server-side — ask for more and it's silently clamped, not rejected. Not everything is a paginated connection: listAuditItemsForAuditor, listAuditReportsForAuditor and listAuditSampleResultsForAuditor return plain arrays, and listAuditItemEvidencesForAuditor returns a single object with an evidences array. getAuditItemCheckEvidenceForAuditor and listAuditFindingsForAuditor are paginated.
Errors
| HTTP | Shape | When |
|---|---|---|
403 | { "isSuccess": false, "error": "Insufficient permissions..." } | During query, your key's role doesn't match this operation's requirements |
403 | { "success": false, "error": "Insufficient area access for this action" } | During action, your key's role doesn't match this operation's requirements |
401 | { "success": false, "error": "User login required!" } | No valid user resolved from your api-key |
400 | { "isSuccess": false, "error": "Invalid request. Please check your query arguments." } | Covers bad arguments and an audit/item/conversation that either doesn't exist or wasn't shared with you — these look identical on purpose, so you can't probe what else is on the platform by comparing error messages |
200 with a business error | { "isSuccess": true, "data": { "errors": [...], "data": ... } } | "No update required", or a missing required field) — always check the response errors, not just the HTTP status. The evidence-review actions return { data, errors } with no top-level success; only the request/conversation actions carry data.success. |
The two download queries are the exception to the
400row above.getManualEvidenceDownloadUrlreturnsisSuccess: truewithdata: nullfor apkthat isn't in your org, on the same no-probing reasoning, and for evidence that's an external link rather than an upload.getAuditItemPolicyDownloadUrlForAuditorreturnsnullwhen the policy has no approved version — a mapped policy can still be an unpublished draft. Treatnullas "no file available to you", not as a failure to retry.
Asking for a policy or check that isn't attached to the audit item you named lands in the
400bucket above, indistinguishable from a bad argument — the mapping is the authorization, so a miss can't be told apart from a typo.
Internal error detail (stack traces, database errors) is never included in a response — check the generic message bucket you landed in above rather than trying to pattern-match on specific wording.
Best practices
- Use the filtered spec (
?tags=audit) so your integration only builds against operations you'll actually call. - Check the action response body, not just the HTTP status — an action can return 200 and still have rejected your input. The evidence-review actions signal this via an empty/populated errors array (no top-level success field); the evidence-request and conversation actions carry data.success. Check errors in both cases
- Poll with
updatedSince, not full re-pulls — every list endpoint here supports it, and it's how your integration should pick up changes. Pair it with a periodic full resync of evidence, for the reason in Incremental sync. - Keep request bodies under 500kb. This applies across every endpoint, including bulk actions like
BULK_UPDATE_EVIDENCE_REVIEW_STATUS— batch large jobs into multiple calls rather than one large payload. - Pace your integration, especially on sync jobs. Use
updatedSinceto fetch only what changed instead of re-pulling everything, and space out bulk operations rather than firing large bursts of concurrent requests. Traffic that looks abusive can result in your key being revoked. - Implement exponential backoff on 429 (respect
Retry-After).
FAQ
Can I see every audit in my client's org? No — only audits explicitly shared with your specific auditor user. If a colleague needs access, the client adds them as their own auditor user and grants it separately.
What happens if the client revokes my access mid-sync? The audit stops appearing in listAuditsForAuditor and any direct call for it returns the same "not found" response as an audit that never existed. This takes effect on your very next request — there's no caching delay. If the revoked audit was your last grant, your key is deactivated too — a sudden 401 means "all access revoked," not a transient error.
How do I flag a control that needs more information from the client? Set its evidenceReviewStatus to INFO_REQUESTED via UPDATE_EVIDENCE_REVIEW_STATUS and include a conversationMessage in the same call — the client sees both the status change and your note together, in their normal queue.
Can I download evidence files through the API? Yes. Call getManualEvidenceDownloadUrl with an evidence pk from listManualEvidences. It returns a time-limited link you can fetch directly, with no api-key header and no browser session — which is what makes it usable from a sync job or an audit workspace. Links last 30 minutes; re-call the query for a fresh one.
Is there a bulk export of all evidence? No. Download is one call per evidence record, and there's no ZIP or archive endpoint. Page through listManualEvidences, skip nodes where fileName is null (those are external links, not uploads), and fetch each file's link as you go rather than minting them all up front.
Can I get the policy documents mapped to an audit? Yes. Call listAuditItemEvidencesForAuditor for the item, take the pk of a POLICIES row, and pass it to getAuditItemPolicyDownloadUrlForAuditor with the same auditItemPk. Policies without an approved version return null — there's no document to fetch yet.
Can I see automated check results? Yes, per item, via getAuditItemCheckEvidenceForAuditor. It returns the evidence rows collected inside the audit period, plus currentCheckStatus with Sprinto's passing/failing counts. Those counts are current state and are not scoped to the audit period — see the caveat in that section.
A check shows no evidence through the API, but I can see it passing in Sprinto — why? Almost always the audit's evidence-collection window. The audit view answers "what evidence exists inside the audit period"; the Monitoring page shows a check's current state and isn't scoped to any audit. If the check's data was collected outside the audit window, the audit view is empty — for the client's admins too, not just for you. Ask the client to confirm the audit period covers the dates you expect.