API documentation
SC Checker API 0.1.0
The machine-readable document is served by the API host at /v1/openapi.json. Keys are created on the API settings page.
Request crawls of a verified SuiteCommerce domain and read their results.
Availability
Enterprise and Partner plans. The public API is served on its own host, which answers the operations that carry an `x-scope`, this document at `/v1/openapi.json` and `/v1/health`, and nothing else. The other operations in this document are the web app’s, served on the private network under a signed-in session; a key reaching one is a 404. Every operation is contract-tested against the schemas the server validates with. PayPal’s webhook reaches the service through the web app and is not part of this document.
Authentication
`Authorization: Bearer scc_live_…`: `scc_live_` and 22 letters and digits. A key is shown once, when it is created or rotated, and is stored only as a keyed hash, so it cannot be shown again. Every refusal of a key is the same 401. A rotated key keeps working for 24 hours beside its successor. `scc_test_` keys are reserved: none is issued, and one presented is refused.
Scopes
Each operation a key may call names the one scope it needs in `x-scope`. A key without it is refused 403 `insufficient_scope`, naming the scope. The scopes are `domains:read`, `crawls:write`, `crawls:read`, `reports:read` and `webhooks:write`.
Rate limits
Per key and per organisation, counted across every server. Every response carries `X-RateLimit-Limit`, `X-RateLimit-Remaining` and `X-RateLimit-Reset`; a 429 carries `Retry-After` in seconds.
Idempotency
Every mutating call accepts `Idempotency-Key`. The key is matched together with a hash of the body: the same key with a different body returns 409 rather than the earlier result. The four operations whose answer is a credential — creating or rotating a key, creating a webhook endpoint, rotating its secret — are marked `x-idempotency-replay: false`: their answer is never stored, and any repeat of the key is a 409.
Webhooks
Every delivery is a POST of `WebhookPayload` carrying `X-SCChecker-Signature: t=<unix>,v1=<hex>`, the HMAC-SHA256 of `t + "." + body` under the endpoint’s signing secret. Reject a `t` more than five minutes from your own clock. For 24 hours after a secret is rotated the header carries a second `v1`; accept the delivery when any verifies. Delivery is at least once and `id` is the same on every retry: use it to discard a repeat.
Pagination
Cursor only. An offset over a table taking inserts skips and duplicates rows.
Errors
One envelope at every status: `{ error: { code, message, requestId } }`. Branch on `code`; `message` is for a person and may be reworded in any release.
Operations
GET /v1/crawls
List crawls
Scope
crawls:readPOST /v1/crawls
Request a crawl
Scope
crawls:writeSpends credits at enqueue, not at completion. The depth decides the cost and the three budgets; everything else is derived server-side from the org’s entitlement, which is re-read inside the admission transaction.
GET /v1/crawls/{id}
Read a crawl
Scope
crawls:readGET /v1/crawls/{id}/events
Stream a crawl’s progress
Scope
crawls:readServer-Sent Events over `crawl_events`. Each frame is `id: <ulid>`, `event: <type>`, `data: <CrawlEvent>`; a `: hb` comment every fifteen seconds keeps proxies from idling the connection. Send `Last-Event-ID` on reconnect to resume after that row. The stream closes after the `terminal` frame or after ten minutes, whichever comes first. Ownership is re-checked on every connection.
GET /v1/domains
List verified domains
Scope
domains:readRead-only. Adding a domain needs proof of control — e-mail at the registrable domain, a DNS TXT record, or a tag in the SMT head — none of which an API key can supply, so it stays a wizard in the web app.
GET /v1/crawls/{id}/report.pdf
Download a report as a PDF
Scope
reports:readThe full report, rendered once and stored. 202 with `Retry-After` while it renders — the render is enqueued idempotently, so repeated calls are one job. `not_entitled` for a plan without PDF export or an audit outside the plan’s history window; `feature_unavailable` when this deployment has no renderer. The document’s date is the report’s own `generatedAt`, so a regenerated PDF of the same report is the same document.
GET /v1/domains/{id}
Read a domain
Scope
domains:readOne domain this organisation holds a claim on. A domain another organisation owns is a 404, indistinguishable from one that does not exist.
GET /v1/crawls/{id}/report
Read a crawl’s report
Scope
reports:readThe latest version of the report, projected through the plan: `visibility: full` for a plan with the full report and an audit inside its history window, the stored `summary` otherwise — a 200 either way, never a refusal, so a client can tell that the answer exists. `watermark` names the partner on a partner audit. Benchmark percentiles appear only on plans that include them. `format=markdown` answers the same projection as `text/markdown`. 404 until the crawl has a report.
GET /v1/webhooks
List webhook endpoints
Scope
webhooks:writeEvery endpoint, oldest first. `display` is the host and the last four characters: a receiver URL is a credential and is never returned. `limit` is the most endpoints an organisation may hold.
POST /v1/webhooks
Register a webhook endpoint
Scope
webhooks:writeAn https URL on a public hostname, and the events to send it. The URL is encrypted at rest and every delivery goes through the same guard as the crawler: public addresses only, and a redirect is refused. `secret` is in this response and nowhere else; every delivery is signed with it (see the document’s `webhooks`). `feature_unavailable` when this deployment has no encryption key; `destination_limit` past the most endpoints an organisation may hold.
GET /v1/webhooks/{id}
Read a webhook endpoint
Scope
webhooks:writeThe endpoint and its ten most recent deliveries: each one’s status, the receiver’s HTTP status and a closed code. A receiver’s response body is never read or stored.
PATCH /v1/webhooks/{id}
Change a webhook endpoint
Scope
webhooks:writeIts events, its description, or whether it is enabled. Enabling an endpoint that was disabled — the receiver answered 410, or failed too many times in a row — clears the reason and the count. The URL cannot be changed: register a new endpoint.
DELETE /v1/webhooks/{id}
Remove a webhook endpoint
Scope
webhooks:writeThe encrypted URL and secret, and the delivery history, go with it.
POST /v1/webhooks/{id}/test
Send a test delivery
Scope
webhooks:writeOne signed `ping` now, and what the receiver answered: its status, never its body. Recorded with the deliveries; a failed ping never counts toward disabling the endpoint. Limited per endpoint per hour.
POST /v1/webhooks/{id}/rotate-secret
Rotate a webhook signing secret
Scope
webhooks:writeReturns the new secret once. For 24 hours deliveries carry a second `v1` under the previous secret, so a receiver can switch without missing one.