Public API
The stable /v1 REST surface for external clients, agents, and MCP integrations
squirrelscan exposes a stable, programmatic REST API under /v1. It’s the
surface external clients, agent workflows, and MCP integrations can depend on.
Base URL
https://api.squirrelscan.comAuthentication
Every request authenticates with a bearer token in the Authorization header:
curl https://api.squirrelscan.com/v1/credits \
-H "Authorization: Bearer <token>"Programmatic API keys are prefixed sq_ and are the intended public auth
mechanism (rolling out, see the API-keys epic). Today the same scheme also
accepts CLI tokens (sqcli_ prefix, issued by squirrel auth login) and
dashboard session JWTs, so you can start integrating immediately.
Public surface
These are the external-client-facing routes covered by the contract:
| Resource | Endpoints |
|---|---|
| Audits | POST /v1/agent-runs, GET /v1/agent-runs, GET /v1/agent-runs/{id}, GET /v1/agent-runs/{id}/report |
| Reports | POST /v1/reports, GET /v1/reports, GET /v1/reports/{id}, PATCH /v1/reports/{id}, DELETE /v1/reports/{id} |
| Credits | GET /v1/credits |
| Organizations | GET /v1/organizations/{id} |
Start a run with a rule filter
POST /v1/agent-runs takes the run’s settings as a config object. This request audits one page and runs the ax rules plus core/robots-meta, minus the three cloud-backed ai/ rules. It skips the headless browser and external link checks, and treats the site as a one-shot check:
curl -X POST https://api.squirrelscan.com/v1/agent-runs \
-H "Authorization: Bearer $SQUIRREL_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"url": "https://example.com",
"config": {
"maxPages": 1,
"enabledRulePatterns": ["ax/*", "core/robots-meta"],
"disabledRulePatterns": ["ai/page-type-match", "ai/site-metadata", "ai/llm-parsability"],
"render": false,
"externalLinksEnabled": false,
"ephemeral": true
}
}'The response is 201 with the run id. Poll GET /v1/agent-runs/{id} until status is completed, then read GET /v1/agent-runs/{id}/report. With an API key trigger defaults to api; a signed-in session has to send it. config may also be sent as a JSON-encoded string, the older form.
Two sets, like a lint config
enabledRulePatterns is the include set: only the rules it selects run. disabledRulePatterns is the disable set: the rules it names do not run. A run’s rules are the include set minus the disable set, and disable wins: a rule named in both does not run. Leave enabledRulePatterns out to start from every rule. The website’s own disable_rules are added to the disable set.
Both lists take the same patterns, with the same matching, limits and errors as the CLI’s --rule-include and --rule-exclude: a category (ax), a category glob (ax/*) or an exact rule id (core/robots-meta). List ids with GET /v1/rules. At most 50 patterns of at most 100 characters each, using letters, digits and -_./*: no commas, spaces, regular expressions or control characters. Opt-in rules (gaps/*) cannot be selected.
config key | What it does |
|---|---|
enabledRulePatterns | The include set, above. Never empty. |
disabledRulePatterns | The disable set, above. May be empty. |
render | false fetches every page over plain HTTP with no per-page headless browser. true renders every page, within what the plan allows. Leave it out to follow the website’s setting. Rendering does not change the price. |
externalLinksEnabled | Check external links for this run. Each distinct link the cloud verifies costs 1 credit, so a one-page check that does not need them sends false. |
editorSummary | Build the editor’s summary, an LLM-written overview of the whole report that adds about 30 seconds to a run. Leave it out for the default: skipped when enabledRulePatterns is set (a filtered run), built on every other run. true builds it on a filtered run. false skips it on any run. No credit effect: the summary has no credit charge, built or skipped. |
ephemeral | The same one-shot check as ephemeral on the MCP run_audit. If the run creates the website, that website skips the plan’s website limit, stays out of website listings and is archived once the run ends. The run and its report are not purged, until a purge ships: GET /v1/agent-runs/{id} and the report keep working, and the run stays in GET /v1/agent-runs. An organization can hold at most 5 live one-shot websites (429, code EPHEMERAL_LIMIT). If the organization already tracks the domain, or you send websiteId, the run uses that website, nothing is deleted, and the response has ephemeral: false with an ephemeralIgnoredReason. Cannot be combined with auditId. |
A request the API cannot honour as written is a 400 with code VALIDATION_ERROR, and nothing is charged or started: invalid JSON, a value that is not an object, an unknown config key, a wrongly typed maxPages, coverageMode, externalLinksEnabled, render, editorSummary or ephemeral (the issue path is config.editorSummary for the summary flag), or a bad rule list. For a rule list, error.patterns names the entries at fault: an unknown category, a pattern that matches no rule (core/robots-metaa), or a list over the limits. A selection that the disable set leaves with no rule to run is refused too. The other refusals use their own codes: 402 INSUFFICIENT_CREDITS (checked before a one-shot website is created), 429 QUEUE_FULL and EPHEMERAL_LIMIT, 404 WEBSITE_NOT_FOUND, 409 AUDIT_ID_IN_USE.
The create response carries editorSummary (boolean) and, when it is false, editorSummarySkippedReason (rule-filtered run or disabled by the run config). Both appear only when enabledRulePatterns or editorSummary was sent.
The config a run reports in GET /v1/agent-runs/{id} can be sent back: the keys the server writes into it (auditLevel, pageLimitNotice, creditClamp, renderMode and the like) are accepted and dropped. The report of a run started with enabledRulePatterns carries a sibling includeFilter (the patterns and how many rules ran), because its scores and issue counts are not comparable to a full audit’s.
Opt in to the editor’s summary
A filtered run skips the editor’s summary by default, so the one-page request above needs no flag. To get it back, send editorSummary: true:
{
"url": "https://example.com",
"config": {
"maxPages": 1,
"enabledRulePatterns": ["ax/*", "core/robots-meta"],
"render": false,
"ephemeral": true,
"editorSummary": true
}
}A skipped summary is absent from GET /v1/agent-runs/{id}/report (the editorSummary field is not there, not null). With editorSummary: true it is present as on any other run.
Speed of a filtered run
A one-page filtered run skips the work no selected rule reads: the editor’s summary, the external link check (when no selected rule reads link results: links/dead-links, links/broken-external-links, reliability/broken-forms, integrity/known-malicious-url) and any cloud service no selected rule reads. Skipped steps are recorded in the run timeline, below. How long a run takes still depends on the site, so treat any timing as an estimate.
Run timeline and skipped steps
While a run is pending or running, GET /v1/agent-runs/{id} returns its latest progress event (phase, a message and a page count). A step the run skips is recorded as a single skipped event with a reason and no duration, for example editor_summary skipped: rule-filtered run. The phase names a polling client may see for skipped steps are:
| Phase | Reasons |
|---|---|
editor_summary | rule-filtered run, disabled by the run config |
cloud_prefetch | no enabled rule reads a cloud service |
external_links | no enabled rule reads external link results |
render_prefetch | render disabled by the run config, the crawl rendered every page (filtered runs only) |
The set of phase names is not a stable enum: match on the ones you know and ignore the rest. An API key sees only the latest phase through progress. The full event list is a signed-in view in the dashboard. Skipping a step never changes the price of the steps that ran, and nothing is charged or refunded for a skipped one.
Error envelope
All errors use one stable, typed shape:
{
"error": {
"code": "NOT_FOUND",
"message": "Report not found",
"requestId": "8a1f…"
}
}code: stable, machine-readable (SCREAMING_SNAKE_CASE). Match on this. Treat unknown codes as opaque and fall back tomessage.message: human-readable; may change. Don’t match on it.requestId: present when available; quote it to support for log lookups.
Common codes: UNAUTHORIZED, FORBIDDEN, NO_ORG, NOT_FOUND,
VALIDATION_ERROR, INVALID_JSON, PAYLOAD_TOO_LARGE, SECURITY_VIOLATION,
INSUFFICIENT_CREDITS (with error.required / error.balance),
DUPLICATE_REQUEST, RUN_NOT_ACTIVE, UPSTREAM_ERROR (with error.reason),
RATE_LIMITED, QUEUE_FULL, EPHEMERAL_LIMIT.
SECURITY_VIOLATION (400) is returned by POST /v1/reports when the report
payload trips the content security scanner; error.threats lists the matched
patterns.
5xx responses from unhandled server errors (DB/storage failures not caught by
a handler) are not guaranteed to follow the envelope: handle them by status
code and retry idempotent reads with backoff.
Rate limiting
Rate limiting is being rolled out. When enforced, throttled requests return
429 Too Many Requests with these headers:
| Header | Meaning |
|---|---|
RateLimit-Limit | Request quota for the current window |
RateLimit-Remaining | Requests remaining in the window |
RateLimit-Reset | Seconds until the window resets |
Retry-After | Seconds to wait before retrying |
Back off on Retry-After and retry idempotent reads.
Credits
Cloud operations (publishing reports, cloud audits) are priced in
credits. When an organization runs out, the API returns
402 with code: "INSUFFICIENT_CREDITS".