API overview
Authentication, scopes, conventions, errors, pagination, filters and the resource map for the ScoutSentinel REST API.
v1Updated
The API lives at https://api.scoutsentinel.com with every resource under /v1. It is what the product application itself uses, so anything you can do in the app has an endpoint; a subset of routes is also open to API keys, and those are the ones documented here.
Authentication
Two credentials are accepted on the public API.
API keys. Create one under Settings, API keys (POST /v1/api-keys, admin role). A key has a name, one or more scopes, and an optional expiresAt. Keys currently grant those permissions across the organisation, not one environment. Creating an environment-restricted key is not supported: a supplied environmentId is rejected rather than silently ignored. Existing keys have not gained an environment restriction. The secret is shown once; afterwards only the visible prefix (for example ss_live_3f9a) is listed. Send it as a bearer token:
GET /v1/journeys?status=healthy,degraded&range=7d HTTP/1.1
Host: api.scoutsentinel.com
Authorization: Bearer ss_live_<prefix>_<secret>
Keys are hashed at rest and cannot be recovered; revoke with DELETE /v1/api-keys/{apiKeyId} and create a replacement. There is no overlap period built into the API; run two keys side by side while you roll over.
Session cookie. The product application signs in through /v1/auth/* and receives an ss_session cookie (HttpOnly, Secure, SameSite=Lax, scoped to .scoutsentinel.com). Mutations from a browser session also carry the CSRF token from GET /v1/auth/csrf in the X-CSRF-Token header. Sessions idle out after 30 days of inactivity and expire absolutely after 90 days. Session-only routes (member and API key management, approvals, previews, billing) are marked session below.
Scopes
An API key carries a subset of the actions its creator’s role allows. The key’s effective role is the least privileged role that covers every scope.
| Scope | Grants |
|---|---|
journeys:read |
Journeys, runs, performance, errors, locations, releases list, locations catalog |
journeys:write |
Create and update journeys |
watches:read |
Watches and their version history |
watches:write |
Create watches and versions, activate, pause, retire |
watches:run |
Run a journey now |
events:read |
Event list, detail and stats |
events:write |
Acknowledge, assign, resolve, dismiss |
evidence:read |
Evidence metadata and download tokens |
releases:write |
Record release markers |
reports:read, reports:write |
Reports |
assets:read |
Exposure findings |
integrations:read |
List integrations |
graph:read |
Shared observation graph |
Roles for users are owner, admin, member and viewer; viewers are read-only everywhere, members manage journeys, watches, events and reports, admins add approvals, integrations, members, API keys, secrets, environments and retention, and owners can delete or transfer the organisation.
Conventions
- JSON request and response bodies,
Content-Type: application/json. Every body, path parameter and query string is validated against a published zod schema before a handler runs. - IDs are UUID strings (UUIDv7). Timestamps are ISO 8601 with an offset. Durations are milliseconds, sizes are bytes, money is in integer minor units.
- Environments. Where an endpoint supports an
environmentIdquery parameter, use it to filter that request. A filter is not a key permission boundary: API keys currently apply across the organisation. Check each endpoint’s query schema rather than assuming a global header or default-environment restriction. List environments withGET /v1/environments. - Watch definitions are validated with the core schema and cross-field checks described in the step DSL reference; materiality fields with defaults may be omitted on input and are always present in responses.
- Secrets never round-trip. Tenant secrets return metadata only, integration configs return
{ "set": true, "last4": "…" }in place of secret fields, and API key secrets appear once in the create response. - Outcome and behaviour are always separate fields (
outcome,behaviourState);unknownis a first-class outcome withunknownReason. 204responses carry no body.202means the work continues asynchronously and the body tells you what to poll.
Errors
Errors are RFC 9457 problem details with Content-Type: application/problem+json. type is https://api.scoutsentinel.com/problems/<code> and the same code is repeated in the body.
{
"type": "https://api.scoutsentinel.com/problems/validation_failed",
"title": "Validation failed",
"status": 400,
"detail": "2 field(s) failed validation",
"code": "validation_failed",
"traceId": "4bf92f3577b34da6a3ce929d0e0e4736",
"errors": [
{ "path": "definition.probes.0.steps.3.type", "code": "custom", "message": "step type \"dns.resolve\" is not allowed in a \"browser\" probe" },
{ "path": "definition.schedule.everySeconds", "code": "too_small", "message": "Too small: expected number to be >=60" }
]
}
code |
Status | When |
|---|---|---|
validation_failed |
400 | Body, params or query failed schema validation; errors[] lists each issue |
unauthorized |
401 | Missing, expired or revoked credential |
forbidden |
403 | Role or scope does not allow the action |
csrf_failed |
403 | Session mutation without a valid X-CSRF-Token |
not_found |
404 | No such resource in this organisation (also used when an environment id is not yours) |
conflict |
409 | The request conflicts with current state, for example activating a retired Watch |
payload_too_large |
413 | Body over the limit |
unprocessable |
422 | Valid shape, but the request cannot be carried out |
rate_limited |
429 | Too many requests; retryAfter (seconds) is in the body and Retry-After in the headers |
entitlement_exceeded |
422 | A plan limit was hit; entitlement: { key, limit, current, plan } explains which |
approval_required |
4xx | The change needs an admin approval before it takes effect; the version is stored and approvalId is returned. The status code for this case is not fixed in the contract yet |
internal_error |
500 | Something on our side; quote traceId to support |
Rate limits
Credential endpoints are rate limited per account and per IP. Limits on the resource API depend on your plan and are not published as fixed numbers here; a 429 always carries Retry-After, so back off for that many seconds and retry. Poll endpoints such as journey runs at no more than the Watch’s own interval.
Pagination
List endpoints take cursor and limit (default 25, maximum 100) and return:
{ "items": [], "nextCursor": null }
Pass nextCursor back as cursor until it is null. Cursors are opaque and only valid for the same query.
Time range and filters
- Ranged reads accept either
range(1h,24h,7d,30d) or bothfromandto(ISO 8601), never both; with neither, the API applies24h. Responses that depend on the range echo the resolvedperiod: { from, to, preset }so percentages are always labelled. - Multi-value filters are comma separated:
status=open,acknowledged,severity=critical,high,outcome=fail,unknown. locationKeyfilters by vantage point using theprovider:networkClass:regionform, for examplecloudflare:edge:lhr.sortandorder(asc,desc) are per resource;searchmatches names and titles.
| Resource | Filters |
|---|---|
| Journeys | status, behaviourState, channel, subjectId, search, includeArchived, sort (name, status, availability, lastRun, createdAt) |
| Journey runs | outcome, behaviourState, probeType, watchId, locationKey, time range |
| Events | status, severity, kind, category, journeyId, watchId, assetId, assignee (me, unassigned or a user id), search, sort (openedAt, severity, lastObservedAt), time range |
| Evidence | journeyId, watchId, observationId, eventId, kind, stepId, objectAvailable, time range |
Resources available to API keys
| Method and path | Scope | Notes |
|---|---|---|
GET /v1/organisation |
organisation:read |
Organisation summary |
GET /v1/environments |
organisation:read |
Environments |
GET /v1/entitlements |
organisation:read |
Plan limits: Watch count, minimum interval, evidence retention days |
GET /v1/locations |
journeys:read |
Selectable probe locations with locationKey, class, entitlement and online status |
GET, POST /v1/journeys |
journeys:read, journeys:write |
Create with subjectId or an inline subject, and optionally an initialWatch definition or a templateId |
GET, PATCH /v1/journeys/{journeyId} |
journeys:read, journeys:write |
Detail with stats, steps, latest run and coverage |
POST /v1/journeys/{journeyId}/run |
watches:run |
Run now; 202 with the probe runs that were queued |
GET /v1/journeys/{journeyId}/runs |
journeys:read |
Observations; GET .../runs/{observationId} returns the full summary, assertion results, evidence and fingerprint diff |
GET /v1/journeys/{journeyId}/performance, /errors, /locations |
journeys:read |
Timing series, failure history, per-location comparison |
GET, POST /v1/journeys/{journeyId}/watches |
watches:read, watches:write |
Watches on a journey; POST takes { name, definition, activate? } |
GET, PATCH /v1/watches/{watchId} |
watches:read, watches:write |
Current definition, requiredSecrets, missingSecrets |
POST, GET /v1/watches/{watchId}/versions |
watches:write, watches:read |
New version { definition, changeNote?, activate? }; the response says whether approval is required |
GET /v1/watches/{watchId}/versions/{version} |
watches:read |
One version |
GET /v1/watches/{watchId}/versions/diff?from=&to= |
watches:read |
Structured diff with per-change impact |
POST /v1/watches/{watchId}/activate, /pause, /retire |
watches:write |
State changes with an optional reason |
GET /v1/events, GET /v1/events/stats |
events:read |
Work queue and counts by status, severity and kind |
GET /v1/events/{eventId} |
events:read |
Detail with timeline, comparison, coverage and explanation |
POST /v1/events/{eventId}/acknowledge, /assign, /resolve, /dismiss |
events:write |
Dismiss takes a reason: expected_change, test_traffic, duplicate, not_actionable, false_positive, other |
GET /v1/evidence, GET /v1/evidence/{evidenceId} |
evidence:read |
Metadata only |
POST /v1/evidence/{evidenceId}/download-token |
evidence:read |
Short-lived signed URL; writes an access audit row |
GET /v1/integrations |
integrations:read |
Configured integrations, secrets redacted |
POST, GET /v1/releases |
releases:write, journeys:read |
Release markers for change correlation |
Everything else (members, API keys, secrets, retention, integrations management, notification channels, approvals, previews, suppressions, exposure sources, billing) is session-only and lives under the same /v1 prefix.
Example: open Events for one journey
GET /v1/events?journeyId=0192b7d0-4a1e-7c3b-9f2e-6a1d2c3b4a5f&status=open&range=24h HTTP/1.1
Host: api.scoutsentinel.com
Authorization: Bearer ss_live_<prefix>_<secret>
{
"items": [
{
"id": "0192b7e2-1f6a-7d55-8b0c-2e9f4a6b8c1d",
"title": "Buy online: step 7 \"Complete purchase\" HTTP 502 from payment provider (2 consecutive runs, 3 locations)",
"detail": "Failing since 2026-09-17T04:25:04Z across 2 runs from cloudflare edge lhr, cloudflare edge fra, cloudflare edge syd.",
"kind": "outcome",
"category": "outcome",
"severity": "critical",
"status": "open",
"journey": { "id": "0192b7d0-4a1e-7c3b-9f2e-6a1d2c3b4a5f", "name": "Buy online", "icon": "shopping-cart" },
"watchId": "0192b7d1-8e2c-7a91-b3d4-5f6e7a8b9c0d",
"assetId": null,
"environmentId": "0192b7c0-0000-7000-8000-000000000001",
"assignee": null,
"openedAt": "2026-09-17T04:31:12Z",
"lastObservedAt": "2026-09-17T04:35:09Z",
"acknowledgedAt": null,
"resolvedAt": null,
"observationCount": 6,
"consecutiveFailures": 2,
"locations": ["cloudflare:edge:lhr", "cloudflare:edge:fra", "cloudflare:edge:syd"],
"hasEvidence": true,
"hasAiExplanation": true,
"unread": true,
"feedback": null,
"supportMinutes": null
}
],
"nextCursor": null
}
GET /v1/events/{eventId} adds the failure boundary (stepIndex, stepId, stepLabel, reason, assertionId), the observation timeline with each observation’s role (causal, supporting, contradicting, resolving, before), a before and after evidence pair, coverage, and the explanation with its model and prompt version.
Compatibility
The API is versioned in the path. Additive changes (new fields, new enum values announced in advance) do not bump the version. Breaking changes ship as a new version with an overlap period announced at the time.