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:

ModuleAvailable operations
PoliciesList policies, get a single policy with its mappings and version history
ControlsList controls, get a single control with compliance overview and mapped artifacts
EvidenceList 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:

RegionBase URL
Indiahttps://api.scrut.io
UShttps://api.us.scrut.io
EUhttps://api.eu.scrut.io
Australiahttps://api.au.scrut.io
  • Use the same base URL for the token endpoint and all /v1 requests. 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"'"
  }'

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.

ScopeAllows
policy:readList and read policies
control:readList and read controls
evidence:readList and read evidence
evidence:writeUpload 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"

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.
  • evidenceDate is required, in Unix epoch milliseconds, and cannot be in the future.
  • note is optional and is stored with the upload.

Request and Response Format

Responses

  • JSON field names use camelCase.
  • Successful /v1 responses use a { data, meta } envelope. data is an object for a single resource and an array for lists.
  • meta.requestId is a unique ID for each request. The same value is returned in the X-Request-Id response header.
  • The HTTP status code tells you whether a request succeeded. Response bodies do not include a success flag.
  • 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, and departments. Pass multiple values as a comma-separated list. Controls also support domains, functionGroupings, and controlScope filters.
  • Use the fields parameter to add columns that are not returned by default. On single-resource endpoints, fields adds expansions such as gaps, documents, versionHistory, attachments, or tickets. For controls, fields can add complianceOverview, mappedArtifacts, and mappedRisks.
  • 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 url only 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.

EndpointLimit
Token endpoint10 requests per 60 seconds
Data endpoints (/v1/*)50 requests per 60 seconds

Responses include these headers:

HeaderDescription
X-RateLimit-LimitMaximum requests allowed in the current window
X-RateLimit-RemainingRequests remaining in the current window
X-RateLimit-ResetUnix timestamp, in seconds, when the window resets
Retry-AfterSeconds 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 statusError codeWhat it means
400validation_failedA parameter or body field is invalid. Check details for the field.
401unauthorizedThe Bearer token is missing, malformed, or invalid.
401token_revokedThe credential that issued the token was revoked.
401token_staleThe token is stale. Request a new token.
403forbiddenThe credential does not have the scope this endpoint requires.
404not_foundThe requested resource does not exist.
409idempotency_key_reused, idempotency_request_in_progressThe idempotency key conflicts with an earlier or in-progress request.
429rate_limit_exceededYou exceeded the rate limit. Wait for the time in Retry-After.
500internal_errorAn unexpected error occurred.
503upstream_unavailableA 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 statusError codeWhat it means
400invalid_requestgrant_type is wrong or a required field is missing.
401invalid_clientThe client ID or secret is wrong, or the credential is revoked or expired.
429temporarily_unavailableYou exceeded the token endpoint rate limit.
503temporarily_unavailableThe 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

MethodEndpointRequired scopeDescription
POST/oauth/tokenNoneIssue a machine access token
GET/v1/policiespolicy:readList policies
GET/v1/policies/{policyId}policy:readGet a policy
GET/v1/controlscontrol:readList controls
GET/v1/controls/{controlId}control:readGet a control
GET/v1/evidencesevidence:read or evidence:writeList evidence
GET/v1/evidences/{evidenceId}evidence:read or evidence:writeGet evidence
POST/v1/evidences/{evidenceId}/attachmentsevidence:writeUpload 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.