Scrut API
Connect your scripts, pipelines, and internal tools to Scrut to read policies, controls, and evidence and upload evidence attachments through a REST API.
The Scrut API gives you programmatic access to your compliance data through a versioned REST interface. Use it to read policies, controls, and evidence and upload evidence attachments directly from your scripts, CI/CD pipelines, or backend services.
What You Can Do With the Scrut API
The API currently covers three modules:
| Module | Available operations |
|---|---|
| Policies | List policies, get a single policy with its mappings and version history |
| Controls | List controls, get a single control with compliance overview and mapped artifacts |
| Evidence | List evidence, get a single evidence record, upload evidence attachments |
Prerequisites
Before you use the Scrut API, make sure you have:
- A client ID and client secret from an API credential. Org Admins create credentials in the Developer Console. See Create API Credentials.
- A secure place to store credentials, such as environment variables or a secrets manager.
- An HTTP client or language of your choice, such as cURL, Node.js, or Python.
Base URL
Send requests to the base URL for the region where your Scrut organization is hosted:
| Region | Base URL |
|---|---|
| India | https://api.scrut.io |
| US | https://api.us.scrut.io |
| EU | https://api.eu.scrut.io |
| Australia | https://api.au.scrut.io |
- Use the same base URL for the token endpoint and all
/v1requests. A credential works only with its own region's base URL. - Resource endpoints are versioned under
/v1. The token endpoint,/oauth/token, is not versioned. - The examples in this article use
https://api.scrut.io. Replace it with your region's base URL.
API Credentials
Every integration authenticates with a client ID and client secret from an API credential. Org Admins create, store, and revoke credentials in Settings → Developer Console. For step-by-step instructions, see Create API Credentials.
Important: Never commit credentials to version control or expose them in client-side code.
Authentication
The Scrut API uses the OAuth 2.0 client credentials grant. You exchange your client ID and client secret for a short-lived access token, then send that token with every /v1 request.
Get an Access Token
Send a POST request to /oauth/token with a JSON body. The token endpoint accepts application/json only, not application/x-www-form-urlencoded.
curl -X POST https://api.scrut.io/oauth/token \
-H "Content-Type: application/json" \
-d '{
"grant_type": "client_credentials",
"client_id": "'"$SCRUT_CLIENT_ID"'",
"client_secret": "'"$SCRUT_CLIENT_SECRET"'"
}'
const tokenResponse = await fetch("https://api.scrut.io/oauth/token", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
grant_type: "client_credentials",
client_id: process.env.SCRUT_CLIENT_ID,
client_secret: process.env.SCRUT_CLIENT_SECRET,
}),
});
const { access_token } = await tokenResponse.json();
import os
import requests
token_response = requests.post(
"https://api.scrut.io/oauth/token",
json={
"grant_type": "client_credentials",
"client_id": os.environ["SCRUT_CLIENT_ID"],
"client_secret": os.environ["SCRUT_CLIENT_SECRET"],
},
)
access_token = token_response.json()["access_token"]
A successful request returns the token object:
{
"access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...",
"token_type": "Bearer",
"expires_in": 3600,
"scope": "policy:read evidence:write"
}
The scope field lists the scopes granted to your credential, separated by spaces.
Token Behavior
- Tokens expire after 3600 seconds (one hour).
- There is no refresh token. Request a new token with your credentials when the current one expires.
- Several tokens from the same credential can be valid at the same time, until each one expires.
- Revoking a credential immediately invalidates all outstanding tokens.
Send the Token
Include the token as a Bearer token in the Authorization header of every /v1 request:
curl https://api.scrut.io/v1/policies \
-H "Authorization: Bearer $SCRUT_ACCESS_TOKEN"
Scopes
Scopes follow the format {resource}:{action} and control which endpoints a credential can call.
| Scope | Allows |
|---|---|
policy:read | List and read policies |
control:read | List and read controls |
evidence:read | List and read evidence |
evidence:write | Upload evidence attachments. Also allows reading evidence. |
A write scope includes read access to the same resource only. Access across modules needs explicit scopes. For example, policy:read does not grant evidence:read.
Calling an endpoint without the required scope returns 403 forbidden.
Quick Example
List all published policies and policies that need review, including their mapped framework IDs:
curl "https://api.scrut.io/v1/policies?status=published,needs_review&fields=mappedFrameworkIds" \
-H "Authorization: Bearer $SCRUT_ACCESS_TOKEN"
const params = new URLSearchParams({
status: "published,needs_review",
fields: "mappedFrameworkIds",
});
const response = await fetch(`https://api.scrut.io/v1/policies?${params}`, {
headers: { Authorization: `Bearer ${accessToken}` },
});
const { data } = await response.json();
console.log(data);
response = requests.get(
"https://api.scrut.io/v1/policies",
headers={"Authorization": f"Bearer {access_token}"},
params={
"status": "published,needs_review",
"fields": "mappedFrameworkIds",
},
)
print(response.json()["data"])
Example response:
{
"data": [
{
"policyId": "pol_88776",
"policyCustomId": "POL-12",
"policyName": "Information Security Policy",
"status": "published",
"department": "Security",
"assignees": [
{ "name": "Alex Rivera", "email": "alex@acme.com", "isPrimary": true }
],
"approvers": [
{ "name": "Sam Lee", "email": "sam@acme.com" }
],
"isRelevant": true,
"nextReviewDate": 1767225600000,
"entities": [
{ "entityId": "ent_123", "entityName": "Acme Platform" }
],
"gapStatus": "no_gaps",
"mappedFrameworkIds": ["fw_iso27001", "fw_soc2"]
}
],
"meta": {
"requestId": "550e8400-e29b-41d4-a716-446655440000"
}
}
Upload Evidence Attachments
Upload files to an evidence record with a multipart/form-data request. This endpoint requires the evidence:write scope and an Idempotency-Key header.
curl -X POST https://api.scrut.io/v1/evidences/evd_44120/attachments \
-H "Authorization: Bearer $SCRUT_ACCESS_TOKEN" \
-H "Idempotency-Key: 7c9e6679-7425-40de-944b-e07fc1f90ae7" \
-F "evidenceDate=1715904000000" \
-F "note=Q2 access review" \
-F "files=@access-review.pdf" \
-F "files=@access-review-signoff.pdf"
A successful upload returns 201 Created:
{
"data": {
"evidenceId": "evd_44120",
"modifiedOn": 1715904123000
},
"meta": {
"requestId": "550e8400-e29b-41d4-a716-446655440000"
}
}
Keep these limits in mind:
- You can upload up to five files per request.
- The combined size of all files in a request must not exceed 100 MB.
evidenceDateis required, in Unix epoch milliseconds, and cannot be in the future.noteis optional and is stored with the upload.
Request and Response Format
Responses
- JSON field names use camelCase.
- Successful
/v1responses use a{ data, meta }envelope.datais an object for a single resource and an array for lists. meta.requestIdis a unique ID for each request. The same value is returned in theX-Request-Idresponse header.- The HTTP status code tells you whether a request succeeded. Response bodies do not include a
successflag. - The token endpoint,
/oauth/token, returns the standard OAuth token object and does not use the{ data, meta }envelope.
Filtering and Extra Fields
- List endpoints accept filters such as
status,frameworkIds,controlIds,entityIds,assigneeEmails,approverEmails, anddepartments. Pass multiple values as a comma-separated list. Controls also supportdomains,functionGroupings, andcontrolScopefilters. - Use the
fieldsparameter to add columns that are not returned by default. On single-resource endpoints,fieldsadds expansions such asgaps,documents,versionHistory,attachments, ortickets. For controls,fieldscan addcomplianceOverview,mappedArtifacts, andmappedRisks. - List endpoints return all matching records in a single array. There is no cursor pagination.
- Download URLs for files hosted by Scrut are never returned. Document and attachment metadata includes a
urlonly for external links you added yourself.
Timestamps
Dates in request bodies, response bodies, and query filters are Unix epoch milliseconds unless noted otherwise. For example, 1735689600000 is January 1, 2025, 00:00:00 UTC.
Heads Up! The X-RateLimit-Reset header is the one exception. It is a Unix timestamp in seconds, not milliseconds.
Idempotency
Every write request to a /v1 endpoint requires an Idempotency-Key header. The key identifies one logical write, so you can retry a failed request without creating duplicate uploads.
- Keys are 1 to 128 characters long and can contain letters, numbers, and the characters
.,_,~, and-. A UUID works well. - Sending the same key with the same request body returns the stored successful response instead of repeating the write. Stored responses are replayed for 24 hours.
- A replayed response includes the header
Idempotent-Replayed: true. - Sending the same key with a different request body returns
409 idempotency_key_reused. - Sending a key while a matching request is still being processed returns
409 idempotency_request_in_progress.
Rate Limits
Rate limits apply per credential, across all tokens issued from that credential.
| Endpoint | Limit |
|---|---|
| Token endpoint | 10 requests per 60 seconds |
Data endpoints (/v1/*) | 50 requests per 60 seconds |
Responses include these headers:
| Header | Description |
|---|---|
X-RateLimit-Limit | Maximum requests allowed in the current window |
X-RateLimit-Remaining | Requests remaining in the current window |
X-RateLimit-Reset | Unix timestamp, in seconds, when the window resets |
Retry-After | Seconds to wait before retrying. Returned only on a 429. |
When you exceed a limit, the API returns 429. Wait for the number of seconds in Retry-After before you retry.
Errors
Data Endpoint Errors
Errors from /v1 endpoints use this shape:
{
"error": {
"code": "validation_failed",
"message": "Request validation failed.",
"details": [
{
"field": "status",
"message": "status contains unknown value(s): unknown"
}
]
},
"meta": {
"requestId": "550e8400-e29b-41d4-a716-446655440000"
}
}
| HTTP status | Error code | What it means |
|---|---|---|
| 400 | validation_failed | A parameter or body field is invalid. Check details for the field. |
| 401 | unauthorized | The Bearer token is missing, malformed, or invalid. |
| 401 | token_revoked | The credential that issued the token was revoked. |
| 401 | token_stale | The token is stale. Request a new token. |
| 403 | forbidden | The credential does not have the scope this endpoint requires. |
| 404 | not_found | The requested resource does not exist. |
| 409 | idempotency_key_reused, idempotency_request_in_progress | The idempotency key conflicts with an earlier or in-progress request. |
| 429 | rate_limit_exceeded | You exceeded the rate limit. Wait for the time in Retry-After. |
| 500 | internal_error | An unexpected error occurred. |
| 503 | upstream_unavailable | A dependent service is temporarily unavailable. Retry later. |
Token Endpoint Errors
Errors from /oauth/token follow the OAuth format:
{
"error": "invalid_client",
"error_description": "Invalid client credentials. Verify client_id and client_secret, or create a new credential in the Developer Console."
}
| HTTP status | Error code | What it means |
|---|---|---|
| 400 | invalid_request | grant_type is wrong or a required field is missing. |
| 401 | invalid_client | The client ID or secret is wrong, or the credential is revoked or expired. |
| 429 | temporarily_unavailable | You exceeded the token endpoint rate limit. |
| 503 | temporarily_unavailable | The authorization server is temporarily unavailable. Retry later. |
Pro Tip! Include the requestId from meta or the X-Request-Id header when you contact support about a failed request. It helps us trace the exact call.
Endpoints
| Method | Endpoint | Required scope | Description |
|---|---|---|---|
| POST | /oauth/token | None | Issue a machine access token |
| GET | /v1/policies | policy:read | List policies |
| GET | /v1/policies/{policyId} | policy:read | Get a policy |
| GET | /v1/controls | control:read | List controls |
| GET | /v1/controls/{controlId} | control:read | Get a control |
| GET | /v1/evidences | evidence:read or evidence:write | List evidence |
| GET | /v1/evidences/{evidenceId} | evidence:read or evidence:write | Get evidence |
| POST | /v1/evidences/{evidenceId}/attachments | evidence:write | Upload evidence attachments |
See the API reference pages for full request parameters, response schemas, and examples.
Contact support@scrut.io or your CSM for further assistance.