Upload Evidence
Use the Scrut API to upload files to an evidence task from your scripts, CI/CD pipelines, or backend services.
You can upload evidence files to Scrut from your own scripts, CI/CD pipelines, or backend services instead of adding them manually in the product. This guide shows you how to find the evidence task you want, upload one or more files to it, and verify that the upload worked.
Files you upload through the API are handled the same way as files you upload manually in Scrut. See Upload Evidence Manually in the Scrut Help Center.
Before You Begin
You need:
- A Read & Write credential, created by an Org Admin in Settings → Developer Console in Scrut. Uploading attachments requires the
evidence:writescope, which only Read & Write credentials have. See Create API Credentials. - An access token requested with that credential. Confirm that the
scopefield in the token response includesevidence:write. See the API Quickstart. - The evidence files you want to upload, saved locally in a supported file type.
The examples use https://api.scrut.io. Replace it with your region's base URL.
Uploading evidence on a recurring schedule, such as quarterly access reviews? Run the same three calls below from a cron job or a CI/CD step.
Find the evidence task
Call GET /v1/evidence and narrow the results with filters to find the evidence task you want to upload to.
curl "https://api.scrut.io/v1/evidence?frameworkIds=14c4c45d-6c31-4097-a02a-ad9cfd04850e&status=not_uploaded,needs_attention" \
-H "Authorization: Bearer $SCRUT_ACCESS_TOKEN"
Response (truncated):
{
"data": [
{
"evidenceId": "92af7e7f-b18e-495b-bf92-61c23ebd34a7",
"evidenceName": "Quarterly Access Review",
...
}
]
}
Copy the evidenceId. You send it as the path parameter in the next step. The evidence list isn't paginated, so it returns every matching evidence task in a single array.
Store the evidenceId for each evidence task your job uploads to, so later runs don't need to look it up again.
Upload the files
Call POST /v1/evidence/{evidenceId}/attachments as multipart/form-data.
Before you upload, check your files against these rules:
| Rule | Limit |
|---|---|
| Files per request | Up to 5 |
| Size per request | The combined size of all files in one request can't exceed 100 MB. |
| File types | See Supported File Types. |
Send these fields in the request body:
| Field | Required | Description |
|---|---|---|
files | Yes | One file per files field. Repeat the field for each file, up to five per request. |
evidenceDate | Yes | The evidence date, as Unix epoch milliseconds. You can send it as a number or a numeric string. Can't be in the future. |
note | No | A note stored with the upload. |
Each upload also needs an Idempotency-Key header. Generate one key per upload and store it with the upload's record in your job. If the request fails with a network error, a timeout, a 429, or a 5xx status, retry it with the same key and the same body so Scrut doesn't upload the files twice.
curl -X POST "https://api.scrut.io/v1/evidence/92af7e7f-b18e-495b-bf92-61c23ebd34a7/attachments" \
-H "Authorization: Bearer $SCRUT_ACCESS_TOKEN" \
-H "Idempotency-Key: 7c9e6679-7425-40de-944b-e07fc1f90ae7" \
-F "evidenceDate=1715904000000" \
-F "note=Q2 access review, identity verification records" \
-F "files=@access-review.pdf" \
-F "files=@appendix.pdf"
import { readFile } from "node:fs/promises";
const evidenceId = "92af7e7f-b18e-495b-bf92-61c23ebd34a7"; // evidenceId from Step 1
const form = new FormData();
form.append("evidenceDate", "1715904000000");
form.append("note", "Q2 access review, identity verification records");
for (const fileName of ["access-review.pdf", "appendix.pdf"]) {
const file = new Blob([await readFile(fileName)]);
form.append("files", file, fileName);
}
const response = await fetch(
`https://api.scrut.io/v1/evidence/${evidenceId}/attachments`,
{
method: "POST",
headers: {
Authorization: `Bearer ${accessToken}`,
// Reuse the key stored for this upload when you retry.
"Idempotency-Key": idempotencyKey,
},
body: form,
},
);
const { data } = await response.json();
console.log(data);
import requests
evidence_id = "92af7e7f-b18e-495b-bf92-61c23ebd34a7" # evidenceId from Step 1
with open("access-review.pdf", "rb") as review, open("appendix.pdf", "rb") as appendix:
response = requests.post(
f"https://api.scrut.io/v1/evidence/{evidence_id}/attachments",
headers={
"Authorization": f"Bearer {access_token}",
# Reuse the key stored for this upload when you retry.
"Idempotency-Key": idempotency_key,
},
data={
"evidenceDate": "1715904000000",
"note": "Q2 access review, identity verification records",
},
files=[
("files", ("access-review.pdf", review)),
("files", ("appendix.pdf", appendix)),
],
)
print(response.json()["data"])
Expected response (201):
{
"data": {
"evidenceId": "92af7e7f-b18e-495b-bf92-61c23ebd34a7",
"modifiedOn": 1711929600000
},
"meta": {
"requestId": "550e8400-e29b-41d4-a716-446655440000"
}
}
If you retry with the same key and body within 24 hours, Scrut returns the stored response with the header Idempotent-Replayed: true and doesn't upload the files again. See Idempotency.
Verify the upload
Call GET /v1/evidence/{evidenceId} with attachments in fields to confirm the files were attached.
curl "https://api.scrut.io/v1/evidence/92af7e7f-b18e-495b-bf92-61c23ebd34a7?fields=attachments" \
-H "Authorization: Bearer $SCRUT_ACCESS_TOKEN"
Each item in attachments includes:
| Field | Description |
|---|---|
displayName | The file name, such as access-review.pdf. |
type | File, Link, or Inline. |
evidenceDate | The evidence date, as Unix epoch milliseconds. |
note | The note stored with the upload, if any. |
addedBy, addedOn | Who added the attachment and when. |
isCurrentPeriod | Whether the attachment belongs to the current evidence period. |
periodStart, periodEnd | The start and end of the evidence period the attachment belongs to. |
The response doesn't include download URLs for files hosted by Scrut.
Note: The API accepts only file uploads and doesn't provide an endpoint to delete or replace an attachment. To add evidence from an external link, use Add Attachment → Use a Link in Scrut.
Supported File Types
| Type | Extensions |
|---|---|
.pdf | |
| Word | .doc, .docx, .dotx |
| Excel | .xls, .xlsx, .xlsm |
| PowerPoint | .ppt, .pptx |
| Text | .txt, .log, .html |
| Markdown | .md |
| YAML | .yaml, .yml |
| CSV | .csv |
| JSON | .json |
| XML | .xml |
| Images | Any image type, such as PNG, JPG, GIF, SVG |
| Video | .mp4, .avi |
| Audio | .mp3, .wav |
| Archives | .zip, .rar |
| Apple iWork | .numbers, .pages |
.eml, .msg | |
| Other | .dwf, .evtx |
Congratulations
You've uploaded files to a Scrut evidence task and confirmed that they were attached. Scrut organizes the files into a date-specific folder based on the evidence date, following the fiscal year format set in Settings. See Upload Evidence Manually.