> 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

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 as it's being reviewed *within a specific audit*. This is what you actually work with day to day — one row per control under review in that audit, each carrying its own review status. `auditItemPk` is the ID you'll use across evidence-review and conversation calls.
* **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 two kinds:
  * **Manual evidence** — a file a person on the client side uploaded.
  * **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.
* **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.
* **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.

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

| Operation                   | Type  | What it returns                                           |
| --------------------------- | ----- | --------------------------------------------------------- |
| `listManualEvidences`       | Query | Uploaded evidence files.                                  |
| `listControlCheckEvidences` | Query | Automated check evidence behind passing/failing controls. |

Both of these 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 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.

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

Every list-shaped operation above accepts `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\" } }"
```

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.

On paginated endpoints (`getConversationMessages`), 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.

**Pagination:** `first` is capped at 100 server-side — ask for more and it's silently clamped, not rejected. `listAuditItemsForAuditor` returns a plain array, not a paginated connection.

## 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": ... } }`                        | <p>"No update required", or a missing required field) — always check the response errors, not just the HTTP status. The evidence-review</p><p>  actions return { data, errors } with no top-level success; only the request/conversation actions carry data.success.</p> |

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

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


---

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