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. An organisation holds as many live keys as its plan includes — one on Enterprise and on Partner — and a successor cannot be rotated until the key it replaced has stopped working, so at most two keys work at once.
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
Counted across every server, in fixed windows; a request over any of them is 429 `rate_limited` with `Retry-After` in seconds. From one client address, 120 requests a minute, counted before the key is read; after 20 refused keys (401) from one address within ten minutes, every request from it is 429 until the ten minutes are up, a valid key included, and the key is not read. Per organisation, 120 requests a minute, its keys and its signed-in sessions in the web app together; per key, 60 a minute.
Creating a crawl is limited to 10 a minute per key, and to 10 a minute and 60 an hour per organisation, whoever asks. Two refunded failures the site caused for one domain within 24 hours pause it until 24 hours after the first (`domain_paused`). Minting, rotating and revoking API keys: 10 an hour per organisation. Creating and removing webhook endpoints: 10 a day per organisation; test deliveries: 10 an hour per organisation. Event streams: 4 open per key.
A response counted against a limit carries `X-RateLimit-Limit`, `X-RateLimit-Remaining` and `X-RateLimit-Reset` (Unix seconds) for the tightest one — the limit with the fewest requests left — success included.
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 five operations whose answer is a credential — creating or rotating a key, creating a webhook endpoint, rotating its secret, creating a share link — 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` — the delivery `id`, the event `type`, and for an event about a crawl its `crawlId`, `reportUrl`, `host` and `environment` — 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. An organisation holds as many endpoints as its plan includes — one on Enterprise and on Partner — enabled or not. Creating and removing endpoints is limited to 10 a day per organisation, and test deliveries to 10 an hour per organisation. Deliveries to one endpoint are paced at 60 a minute; beyond that they wait, and are never dropped or counted as failures.
Verifying an endpoint
An endpoint receives events only once it has proved the URL listens for you. A new endpoint is created disabled, `disabledReason: "unverified"`, and sent one signed test delivery — `"type": "ping"` — carrying a `challenge`. Answer it with any 2xx and the JSON body `{"challenge":"<the value you received>"}` and the endpoint is enabled. Any other answer leaves it unverified; fix the receiver and send a test delivery, which checks the echo the same way. The signing secret is returned after the first ping, so a receiver that checks signatures first verifies on a test. An endpoint disabled because it failed too many times in a row, or answered 410, comes back the same way: a PATCH with `enabled: true` does not re-enable it. A disabled endpoint is sent nothing but test deliveries; one switched off with no reason is sent nothing at all.
Sandboxes
A domain can be linked as the sandbox of one of your production domains: a NetSuite sandbox or staging storefront that uses no domain slot. A `Domain` says which it is now — `environment` is `sandbox` while the link is live, approved or awaiting review, and `productionDomainId` names the production domain; both are `production` and `null` otherwise. A `Crawl` carries the `environment` it ran under, written when it was admitted and never changed by a later unlink, and a sandbox crawl’s report skips the checks that only mean something on a live site. Webhook deliveries carry the crawl’s `host` and `environment`, `null` when the event names no crawl. The release check — a sandbox audit against its production domain’s — is the web app’s.
`verificationMethod` is one of `email`, `dns`, `html`, `operator` (granted by our support team) or `linked` (a sandbox verified by its production domain’s proof). It used to list the first three only, and a domain held on either of the others could not be read: treat a value you do not recognise as verified by us.
Payment hold
An organisation with an unpaid balance — a failed subscription payment not made good within three days, an overdue or unpaid Partner term invoice — or with a payment under dispute, a suspended Partner account or a hold our team placed, is held: every request with its key is 402 `payment_required`, after the key itself has been verified, until it is resolved. Nothing is deleted, and the key works again the moment it is.
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. A failure that is refunded (the site blocked the crawl or could not be reached) costs nothing, so after two of them for one domain within 24 hours the next request for it is 429 `domain_paused` until 24 hours after the first, with `Retry-After` and `resetsAt` in `details`. Requests are also limited per organisation — sessions, keys and the console together — to 10 a minute and 60 an hour (`rate_limited`).
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. Sandboxes are listed with their production domains, each with `environment: "sandbox"` and its `productionDomainId`.
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 PDF export is temporarily unavailable. 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. `environment` and `productionDomainId` say whether it is linked as one of your sandboxes, and of which production domain.
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 the organisation’s plan includes: one on every plan with the API, 0 without it.
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 the feature is temporarily unavailable; `destination_limit` when the organisation already holds the one endpoint its plan includes, enabled or not. Creating and removing endpoints is limited to 10 a day per organisation (`rate_limited`, with `Retry-After`). The endpoint is created disabled with `disabledReason: "unverified"` and sent one signed `ping` at once, carrying a `challenge`; it is enabled — in this response — only when the receiver answers that ping with a 2xx whose body is `{"challenge":"<the value>"}`. Otherwise it stays unverified, receives no event, and a test delivery verifies it later. The secret is in this response, after the ping, so a receiver that checks signatures before answering verifies on a test instead.
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. `enabled: true` resumes an endpoint switched off with no `disabledReason`, and keeps its failure count. An endpoint disabled with a reason — `unverified`, `failures` (it failed too many times in a row) or `gone` (it answered 410) — is enabled only by a test delivery its receiver echoes; asking a PATCH to enable it is 409 `endpoint_unverified` and changes nothing. 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. Creating and removing endpoints is limited to 10 a day per organisation (`rate_limited`).
POST /v1/webhooks/{id}/test
Send a test delivery
Scope
webhooks:writeOne signed `ping` now, carrying a fresh `challenge`, and what the receiver answered: its status, and `verified` when it answered 2xx with the body `{"challenge":"<the value>"}` (read to 4 KiB; other members are ignored). A verified ping enables an endpoint disabled with a reason — `unverified`, `failures` or `gone` — and clears its failure count; it is the only way to. Recorded with the deliveries; a failed ping never counts toward disabling the endpoint. Limited to 10 an hour per organisation, across its endpoints. `endpoint_disabled` for an endpoint switched off with no reason: nothing is sent to it.
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.