Skip to Content

Scans

A scan is a run of the research engine over a set of jurisdictions and regulatory areas. It produces a snapshot. See scans and snapshots for the product-side view.

EndpointPurpose
POST /v1/scansRun a scan (sync by default, ?mode=async to queue)
POST /v1/scans/estimateCost envelope for a config, without running it
GET /v1/scans/{id}Status and progress
GET /v1/scans/{id}/streamServer-Sent Events progress stream
POST /v1/scans/{id}/cancelBest-effort cancellation

Create a scan

POST /v1/scans — bearer auth; rate-limited to 6 scans per hour per key; accepts Idempotency-Key.

Query parameters

ParameterValuesDefaultEffect
modesync, asyncsyncsync blocks up to 120 seconds and returns the snapshot inline if the engine finishes in time; otherwise it returns 202 and the scan continues in the background. async returns 202 immediately.

Request body

FieldTypeRequiredConstraints
jurisdictionsstring[]yes1–20 entries, each under 100 characters. Free text; GET /v1/meta/jurisdictions lists the vocabulary the product uses.
areasstring[]yes1–20 entries, each under 200 characters. Free text; vocabulary at GET /v1/meta/areas.
horizonintegeryesMonths of forward horizon: one of 3, 6, 12, 18, 24, 36.
enginestringnov4 (default), v4.5-alpha, or v4.5-beta. Retired ids (v1, v2, v3, admiral) return 422 with field code engine_deprecated.
modelstringnoModel id from GET /v1/meta/models, or one of the shorthand ids opus, opus48, sonnet, haiku. haiku is rejected on verifier paths (see below).
searchModelstringnoModel for the search pass. Same vocabulary as model.
verificationModestringnoOnly value: in-analyst. Valid on any engine listed above. Setting it defaults realist and auditor to true if you left them unset.
realistbooleannoEnables the realist verification pass.
auditorbooleannoEnables the auditor verification pass.
auditorModelstringnoOnly value: sonnet.
fetchProviderstringnoPage-fetch backend for v4.5-beta only: firecrawl (default) or crw. On any other engine returns 422 with field code incompatible.
translationLanguagesstring[]noLanguage codes to translate the snapshot’s narrative content into: fr, de, es, it, zh, ja, pt. Not validated at request time — an unsupported code simply produces no translation. Translations surface via /executive-narrative?language= and export targetLanguage options.

Constraint interactions, exactly as enforced:

  • verificationMode, realist: true, and auditor: true require an explicit engine of v4, v4.5-alpha, or v4.5-beta in the same body (field code incompatible otherwise). Omitting engine and relying on the v4 default does not satisfy this check — set it explicitly when using verification flags.
  • model: "haiku" is rejected (field code haiku_blocked_on_verifier) when the config is verifier-aware: engine v4.5-alpha or v4.5-beta, or engine v4 with realist or auditor set true.
  • Jurisdiction and area names are not membership-checked — only count and length. Unrecognised names still run; the meta lists are the vocabulary the product itself scans with.

Example — sync

curl https://api.regsn.app/v1/scans \ -H "Authorization: Bearer $REGSN_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "jurisdictions": ["UK", "EU"], "areas": ["AML / KYC", "Sanctions"], "horizon": 12 }'

200 — completed within the 120-second ceiling:

{ "scan_id": "8f3d…", "status": "completed", "snapshot": { "items": ["…"], "trends": ["…"], "executive_summary": {}, "_meta": {} }, "snapshot_id": "a1b2…" }

202 — ceiling exceeded; the scan continues in the background:

{ "scan_id": "8f3d…", "status": "running", "status_url": "/v1/scans/8f3d…", "stream_url": "/v1/scans/8f3d…/stream", "message": "Scan exceeded sync ceiling; poll or stream" }

Example — async with verification

curl "https://api.regsn.app/v1/scans?mode=async" \ -H "Authorization: Bearer $REGSN_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "jurisdictions": ["US"], "areas": ["Capital Markets"], "horizon": 6, "engine": "v4.5-alpha", "model": "sonnet", "verificationMode": "in-analyst" }'

Returns 202 immediately with the same status_url / stream_url shape as above.

Errors

Status · codeWhen
402 budget_exhaustedAccount budget pool depleted. Check GET /v1/usage/budget first.
409 idempotency_key_in_use / idempotency_in_progressReplayed Idempotency-Key with a different body / original still running.
422 validation_errorPer-field reasons in errors[] (codes required, invalid, incompatible, engine_deprecated, haiku_blocked_on_verifier).
429 rate_limit_exceededThe scans bucket is exhausted (6/hour). Honour Retry-After.
503 engine_unavailableSelected engine not loaded in this deployment.

Estimate a scan

POST /v1/scans/estimate — bearer auth; counts against the reads bucket (60/min), not the scans bucket. Returns a cost envelope without queueing anything and without touching your budget.

The estimator buckets on counts of jurisdictions and areas plus horizon and engine — the names themselves are not consulted, so stub strings give the same answer as real ones.

Request body

FieldTypeRequiredNotes
jurisdictionsstring[]yesNon-empty.
areasstring[]yesNon-empty.
horizonintegernoIf present, one of 3, 6, 12, 18, 24, 36. (Unlike scan creation, optional here.)
enginestringnoSame vocabulary and deprecation rules as scan creation.
seekerProvider / analystProvider / translatorProviderstringnoProvider hints; keys as listed by GET /v1/meta/models.
curl https://api.regsn.app/v1/scans/estimate \ -H "Authorization: Bearer $REGSN_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "jurisdictions": ["UK", "EU"], "areas": ["AML / KYC", "Sanctions"], "horizon": 12 }'

200:

{ "low_cents": 38, "expected_cents": 55, "high_cents": 91, "confidence": "high", "tier": 1, "sample_size": 14, "fingerprint": { "scan_mode": "standard", "engine_version": "v4", "jurisdiction_bucket": "1-2", "area_bucket": "1-2", "enabled_exports_count": 0, "provider_mix_hash": "da39a3ee", "date_range_bucket": "medium" }, "estimator_version": "v2.0", "engine": "v4" }
FieldMeaning
low_cents / expected_cents / high_centsCost envelope in US cents. All null on a cold start.
confidencehigh, medium, low, or cold_start.
tier1 exact-match window, 2 relaxed one dimension, 3 global similarity-weighted mean; null on cold start.
sample_sizeHistorical runs behind the estimate.
fingerprintStructured fingerprint of the config; stable across equivalent configs. Absent on cold start.
estimator_version, engineEstimator version and the engine the estimate assumed (v4 if you omitted one).

Errors: 422 validation_error (same field rules as above).

Poll a scan

GET /v1/scans/{id} — bearer auth; reads bucket (60/min).

curl https://api.regsn.app/v1/scans/8f3d… \ -H "Authorization: Bearer $REGSN_API_KEY"

200:

{ "id": "8f3d…", "status": "running", "progress": { "phase": "research", "percent": 62, "message": "…" }, "snapshot_id": null, "created_at": "2026-07-20T09:12:03.000Z", "completed_at": null, "error": null, "cost_cents": null }
FieldNotes
statusrunning, completed, failed, or cancelled.
progress{ phase, percent, message }, updated as the scan advances.
snapshot_idSet once status is completed — fetch it via GET /v1/snapshots/{id}.
errorFailure message when status is failed; otherwise null.
cost_centsActual cost once known, else the pre-scan estimate, else null.

404 not_found for an id that does not exist or belongs to another account.

Stream scan progress (SSE)

GET /v1/scans/{id}/stream — bearer auth; not rate-limited; Content-Type: text/event-stream.

curl -N https://api.regsn.app/v1/scans/8f3d…/stream \ -H "Authorization: Bearer $REGSN_API_KEY"

Every message is a data: line carrying a JSON object whose type field discriminates it (the stream does not use named SSE event: fields — dispatch on type in onmessage):

typePayloadTerminal
progress{ "type": "progress", "phase": "…", "percent": 62, "message": "…" }no
complete{ "type": "complete", "data": <snapshot envelope>, "snapshotId": "a1b2…" }yes
error{ "type": "error", "error": "…" }yes
cancelled{ "type": "cancelled" }yes

Behaviour:

  • On subscribe you immediately receive the current progress state, so late subscribers are never blind.
  • If the scan has already finished, the terminal event (complete, error, or the last stored progress) is emitted once and the connection closes.
  • A comment line : heartbeat is sent every 30 seconds to keep proxies from idling the connection out.
wire format
data: {"type":"progress","phase":"research","percent":35,"message":"…"} : heartbeat data: {"type":"complete","data":{…},"snapshotId":"a1b2…"}

404 not_found for an id that does not exist or belongs to another account.

Cancel a scan

POST /v1/scans/{id}/cancel — bearer auth; mutations bucket (30/min).

curl -X POST https://api.regsn.app/v1/scans/8f3d…/cancel \ -H "Authorization: Bearer $REGSN_API_KEY"

200:

{ "id": "8f3d…", "status": "cancelled" }

Cancellation is best-effort: the job is marked cancelled and stream subscribers receive { "type": "cancelled" }, but engine work already in flight runs to completion server-side — its result is discarded rather than written over the cancelled state.

Status · codeWhen
404 not_foundThe id does not exist or belongs to another account.
409 invalid_stateScan already completed, failed, or cancelled.

Scheduled, recurring scans are a product feature configured in the dashboard — see the product guide. The API runs one scan per request; schedule on your side with cron or an orchestrator, using an Idempotency-Key per tick. The app’s discounted batch lane is likewise product-only — it is not exposed on /v1.

Last updated on