> For the complete documentation index, see [llms.txt](https://docs.sprinto.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.sprinto.com/settings/audit-partner-integration.md).

# 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.
* **Client organisation** — the company being audited. Your API key resolves to one auditor user inside one client org, so a single key only ever sees that org; an auditor firm working several clients holds one key per client. Audits carry the org's identity on them (see Read: audits and controls).
* **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.
* **Audit document** — documentation attached to the audit as a whole rather than to any one control: the system description, control mapping sheet, or statement of applicability. Listed inline on `getAuditForAuditor`; fetched one at a time.
* **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. `auditItemPk` is 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. `OrgEvidenceRequirement` items (evidence-request-list audits) carry requested evidence, mapped policies, manual uploads and checks. `Control` items carry manual uploads, checks and the policies mapped to their control, but never requested evidence. Read the field rather than assuming a shape — the two are genuinely different, not a bug.
  * On an audit built from framework criteria, the audit item **is** the criterion rather than a control: `entityModelName` is `OrgEvidenceRequirement` and that requirement's own `entityModelName` is `FrameworkPrinciple`. Such items carry `mappedControls` — the controls that satisfy that criterion. See Read: audits and controls.
* **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.
* **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` (or `ARCHIVED`).
* **Audit Item (control) review status:** `COLLECTING_EVIDENCE` → `INTERNAL_REVIEW` → `READY_FOR_REVIEW` → `INFO_REQUESTED` → `ACCEPTED`.
  * **`READY_FOR_REVIEW`** is your queue — controls the client considers done and waiting on you.
  * **`INFO_REQUESTED`** is a control you've flagged as needing more from the client.
  * **`ACCEPTED`** is 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.

The two roles are not scoped identically on the write actions that change an audit's state — `ADD_ACTION_ON_CONVERSATION` and `UPDATE_AUDIT_STATUS`. An `AUDITOR` key is confined to audits shared with it through an active grant. An `INTERNAL_AUDITOR` key, which belongs to the client's own staff rather than an external partner, retains org-wide reach on those two. If you are building against an internal-auditor identity, don't assume the scoping described on this page.

## What you can do

### Read: audits and controls

| Operation                  | Type  | What it returns                                                                                                                                |
| -------------------------- | ----- | ---------------------------------------------------------------------------------------------------------------------------------------------- |
| `listAuditsForAuditor`     | Query | Every audit your key has been granted access to, each carrying the client org's identity. 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.

**Identifying the client.** Both calls return an `org` block naming the company under audit. On `listAuditsForAuditor` it sits on each node inside `edges`, not once on the connection, so a row you store in your own system carries its client with it:

```json
{ "org": { "pk": "…", "name": "Acme", "legalName": "Acme Technologies Pvt Ltd" } }
```

Because a key is scoped to a single client org, this block is the same on every row of one response; it is repeated per row for convenience when syncing, not because it varies. Use `pk` as the stable identifier, since both names are editable by the client.

The item-level calls don't repeat this; read it from the audit.

**Criteria, controls and the link between them.** On a control-based audit each item carries its `control` and the `frameworkPrinciples` it satisfies, so grouping items by principle reproduces the criteria view. On an audit built from framework criteria the item is the criterion instead, and the controls that satisfy it arrive as `mappedControls`:

```json
{ "mappedControls": [{ "pk": "…", "number": "72", "shortText": "Access reviews" }] }
```

Only active controls mapped under this audit's own frameworks are listed, ordered by control number. The field is present only on items whose requirement is a criterion; a requirement pointing at anything else does not carry it at all, so check for the key rather than for an empty array.

`getAuditForAuditor` additionally returns `orgFrameworks` — the frameworks this audit runs against, each with `name`, `standard`, `type` and `isEnabled` — plus `activeMonitorEvidenceTypes` and `activeCustomCheckTypes`, the checks live for this audit's window. An `AUTOMATED` or `WORKFLOW` evidence row names a `checkType`; these two lists are what tell you whether that check counts for this audit. `activeMonitorEvidenceTypes` is empty for an org whose audits show custom-check evidence only.

> `legalName` is the registered legal entity and is `null` for a client that hasn't filled in its company profile. The key is always present, so `null` means "no legal name recorded" rather than "this API doesn't report one" — fall back to `name` for display, and don't treat it as an error or a reason to retry. Both fields are returned deliberately rather than Sprinto substituting one for the other, so you can tell a legal entity from a display name when a report or certificate needs the former.

### Audit documents <a href="#audit-documents" id="audit-documents"></a>

| Operation                               | Type  | What it returns                           |
| --------------------------------------- | ----- | ----------------------------------------- |
| `getAuditDocumentDownloadUrlForAuditor` | Query | A time-limited link to one audit document |

`getAuditForAuditor` returns an `auditDocuments` array inline — there is no separate list call, because an audit only ever carries a handful of these. Each row has `pk`, `documentType` (`SYSTEM_DESCRIPTION`, `CONTROL_MAPPING_SHEET`, `STATEMENT_OF_APPLICABILITY` or `OTHERS`), `documentDescription`, `createdAt` and `downloadWith`. `downloadWith` is `null` when the row has no file behind it, and names `getAuditDocumentDownloadUrlForAuditor` when it does. Pass that query the audit's `auditPk` and the row's `pk`. Like the other download links it needs no `api-key` and no session, and expires after 30 minutes.

Only active documents are listed. A document the customer has since removed is omitted from the list and refused on download — belonging to the audit **and** being active is the authorization, so a pk from another audit is refused the same way.

`listAuditsForAuditor` deliberately does not carry documents; fetch the audit to get them.

> A document whose file is still being scanned for malware for the first time currently comes back in the `400` bucket rather than as something retryable. That is a known rough edge; if you hit it, retrying a minute later succeeds.

### 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.                     |
| `ADD_ACTION_ON_CONVERSATION` | Action | Closes (`RESOLVED`) or reopens (`OPEN`) a thread                                                              |

`listAuditItemsForAuditor` returns a `conversationSummary` on every item, carrying `conversationPk`, `conversationStatus` and `conversationMessagesCount`. Pass that pk to `getConversationMessages` to read the thread. An item with no thread yet returns the summary with only its `entityPk`, so a missing `conversationPk` means "no thread", not an error.

You can only read/post on a conversation whose parent control is on an audit shared with you — not any conversation in the org.

> Only `RESOLVED` and `OPEN` are settable. `REPLY`, `EDIT` and `ANSWER_UPDATED` are recorded by the events that cause them and are rejected if set directly. Setting the status a thread already holds is also rejected. As with reading, you can only act on a thread whose parent is on an audit shared with you.

### 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.

A `Control` item's policies reach it through the control rather than through a per-item mapping, so `POLICIES` rows on one carry `mappedAt`, `noteForAuditor` and `reviewStatus` as `null`. There is no per-item mapping row to carry them, and a null there does not mean the customer left the field blank.

Beyond the fields above, each row carries what the audit evidence tab shows next to it:

* `EVIDENCE_REQUESTED` rows carry `dueDate` plus `assignee` and `reviewer` (`pk`, `email`, `fullName`), which is how you tell an outstanding request from an overdue one.
* Every row carries `externalSyncLog` when the customer syncs audit evidence to an external tracker: `status`, `syncFailureReason` and `isPermanentFailure`. A permanent failure captured a provider-side error and will not resolve on a resync; a failure without one may. `null` means nothing has been pushed for that row.
* Rows also carry `evidenceLevelStatusSummary` on the item itself — `totalCount`, `pendingReviewCount`, `rejectedCount` and `reviewCompleteCount` — for orgs with evidence-level review enabled, and `null` for the rest.

### 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, or `null` when 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:

```json
{ "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.

> **`version` is 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't `COMPLETED` or `ARCHIVED`. 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 |

`listAuditItemEvidencesForAuditor` returns a ready-to-fetch `downloadUrl` on `POLICIES` rows, so the common case needs no second call at all. Like manual evidence, the link needs no `api-key` and no session, and expires after 30 minutes.

Policies arrive on two kinds of item and through two different mappings. An evidence-requirement item carries the policies mapped to that requirement; a `Control` item carries the policies mapped to its control. Both appear as `POLICIES` rows and both are downloadable through the same query, so an integration does not need to tell them apart.

A customer can restrict evidence download for auditors on a given audit. On such an audit the policy rows still list, so you can see what evidence exists, but `downloadUrl` and `downloadWith` are both `null` and `getAuditItemPolicyDownloadUrlForAuditor` refuses with "Evidence download is restricted for this audit". This mirrors the product, where every download control is hidden on a restricted audit.

`downloadWith` on the row names `getAuditItemPolicyDownloadUrlForAuditor` whenever the policy has an approved version, and is `null` only when it has none at all.

Call that query — with the `POLICIES` row's `pk` and the same `auditItemPk` — in three cases: the inline link has expired, you want the `MARKDOWN` rendition rather than the default `PDF`, or `downloadUrl` came back null while `downloadWith` was set. `fileType` accepts `PDF` (default) or `MARKDOWN`.

**`downloadUrl: null` has five causes and the row does not distinguish them:** the policy has no approved version yet (a mapped policy can still be an unpublished draft); the approved version has no document of the requested type; the file is withheld by a malware scan; the audit has evidence download restricted for auditors; or the link could not be generated on this call. Do **not** read a null as "the customer never published this policy" — where `downloadWith` is set, retry through it before drawing any conclusion. On a restricted audit `downloadWith` is `null` too, so there is nothing to retry through.

### 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.

**You get the full population, not a drawn sample.** Where the customer has drawn a sample for a check, Sprinto's own evidence drawer shows only the sampled rows, so row counts here will not match that view. There is no way to ask for the sample through the API.

The response also carries `availableGroupKeys`. A check's evidence can be split across several groups and you can only request one at a time via `groupKey`, so this lists the others — without it there's no way to tell more groups exist.

### 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.

Each finding resolves its author as `createdBy` (`pk`, `email`, `fullName`) and, where it was raised against an item, an `auditItem` block naming that item's `control` or `orgEvidenceRequirement`. A finding raised on the audit as a whole returns `auditItem: null`, which is the normal shape rather than missing data.

### Completing an audit

| Operation             | Type   | What it does                       |
| --------------------- | ------ | ---------------------------------- |
| `UPDATE_AUDIT_STATUS` | Action | Marks one of your audits COMPLETED |

`COMPLETED` is the only status an auditor key may set — archiving and deleting are the customer's calls and are refused. `auditCompletedOn` is required and must be a full ISO 8601 date-time; it is your assertion about when the audit finished, not the time of the call.

Completion is **not reversible through this API**. It notifies the customer's admins, resolves the audit's open conversation threads, and can schedule the next audit in a chained series. Your report is uploaded by the customer, not through this API. Confirm it is in place — `listAuditReportsForAuditor` will show it — before marking the audit completed.

`orgEntityAssessmentPk` applies only to orgs running audit assessments; omit it unless the audit details name one. If you do pass it, it must be the assessment belonging to the audit you are completing — any other is refused.

### Audit reports

| Operation                             | Type  | What it does                                                                    |
| ------------------------------------- | ----- | ------------------------------------------------------------------------------- |
| `listAuditReportsForAuditor`          | Query | Report documents uploaded against one of your audits, with type and upload date |
| `getAuditReportDownloadUrlForAuditor` | Query | A time-limited link to one audit report                                         |

`listAuditReportsForAuditor` returns each report's stored `reportS3Key`, which is not fetchable on its own. Pass the report's `pk` together with the audit's `auditPk` to `getAuditReportDownloadUrlForAuditor` for the bytes. Like the other download links it needs no `api-key` and no session, and expires after 30 minutes.

Belonging to the audit is the authorization, so a report `pk` from another audit in the same org is refused exactly as a nonexistent one is. A report the customer has since deleted is refused the same way. `null` comes back when the row carries no file at all.

### 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:

```bash
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`) 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` and `listAuditReportsForAuditor` 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 four download queries are the exception to the `400` row above. `getManualEvidenceDownloadUrl` returns `isSuccess: true` with `data: null` for a `pk` that isn't in your org, on the same no-probing reasoning, and for evidence that's an external link rather than an upload. `getAuditItemPolicyDownloadUrlForAuditor` returns `null` when the policy has no approved version — a mapped policy can still be an unpublished draft. `getAuditDocumentDownloadUrlForAuditor` returns `null` when the document row has no file attached, and `getAuditReportDownloadUrlForAuditor` does the same. Treat `null` as "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 `400` bucket 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 that documents it 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 `updatedSince` to 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.

**How do I tell which client an audit belongs to?** Read the `org` block on `listAuditsForAuditor` or `getAuditForAuditor` — it carries `pk`, `name` and `legalName`. One API key only ever covers one client org, so every audit in a single response shares the same org; if you work several clients, you hold a key per client and key off `org.pk` when merging them into one system. `legalName` is `null` when the client hasn't completed its company profile, so fall back to `name` for display.

**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, and usually without a second call — `listAuditItemEvidencesForAuditor` returns a `downloadUrl` on each `POLICIES` row. Use `getAuditItemPolicyDownloadUrlForAuditor` when that link has expired, when you want the markdown rendition, or when the URL was null but `downloadWith` was set.

**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.


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs.sprinto.com/settings/audit-partner-integration.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
