Triggering a Check
End-to-end walkthrough of registering a domain and a webhook, previewing the cost, submitting a Console check, following it to completion, and reading its results.
This guide walks through a Console check from domain registration to its results. A webhook tells you when the check ends; polling is the fallback.
Prerequisites
- A Console account at console.surfacedby.com.
- Credits in your balance. See Credit model for what a check costs and which credit packs are available.
- A Console API key from API keys in the Console. Live keys start with
sk_live_, test keys withsk_test_. Send it in theX-API-Keyheader.
Test keys
A test key behaves like a live key on every endpoint except check submission: there it validates the request and prices it, but does not debit credits, does not start a check, and returns a placeholder scan_id with "is_test": true and credits_required_for_live. Everything else a test key does is real: domains and webhook endpoints it creates belong to your account. Use a test key to exercise request validation and pricing in CI.
Step 1: Register the domain
Checks run against a Console domain. Create one once and keep its id:
GET https://api.surfacedby.com/api/v1/console/domains lists the domains you already registered.
Step 2: Register a webhook
Register an endpoint once for scan.completed and scan.failed, so SurfacedBy tells you when each check ends instead of you polling for it:
The response carries a signing_secret, returned only this once; store it to verify each delivery. A Console endpoint receives the events of your Console domains, and only those: events from SurfacedBy dashboard workspaces never reach it. Console webhooks covers registration, signature verification, and retries.
Step 3: Preview the cost
Request fields, shared with check submission:
| Field | Required | Meaning |
|---|---|---|
domain_id | Yes | The id of one of your Console domains. |
keywords | No | The prompts to run, up to the per-check maximum in the credit model. Omit to let SurfacedBy generate 20 prompts for the domain. |
platforms | No | Platform keys from the platforms endpoint. Defaults to chatgpt, claude, perplexity. Duplicates count once. |
The response returns credits_required and a line-by-line breakdown. No credits are debited.
Step 4: Submit the check
Send the same body to POST https://api.surfacedby.com/api/v1/console/scans:
The Idempotency-Key header is required when you authenticate with an API key. Resending the same key with the same body returns the original response instead of starting a second check. If a request fails with an error, retry with a new key.
A successful submit returns 202 Accepted with:
scan_id: the id you use to follow the check.credits_charged: the credits debited for this check.balance_after: your balance after the debit.
Possible errors:
402withrequired_credits,current_balance, and apurchase_urlwhen your balance is too low.409scan_already_runningwhen another check is pending or running for the same domain.404domain_not_foundwhen thedomain_idis not one of your Console domains.422validation_errorfor an unknown platform key or more prompts than the per-check maximum.422platform_unavailablewhen a requested platform is paused right now. Theunavailable_platformsfield lists which; nothing is charged. Leave them out or retry later.
Step 5: Follow the check
status moves from pending to running, then ends as completed, failed, or cancelled. A check usually takes several minutes; more prompts and platforms take longer. Transient errors during a run are retried before a check is marked failed.
With the webhook from Step 2. When the check ends, your endpoint receives a signed scan.completed or scan.failed event whose data.scan_id is the id from Step 4. Deliveries share the envelope described in Console webhooks (id, type, created_at, livemode, data); keys arrive sorted alphabetically, so parse the JSON rather than relying on key order. A scan.completed for a Console check looks like this:
results_urlis where the check's results are served (Step 6). Fetch it with your API key.is_first_checkistruefor the first completed check on the domain.results_summarycarries the check's Brand Coverage (brand_coverage_pct),total_citations, andduration_seconds.
A scan.failed event carries scan_id, domain_id, domain, measurement_scope, measurement_label, and error_summary, a short explanation written for customers. A failed check is refunded in full (see Handling failures).
Without a webhook, poll. GET https://api.surfacedby.com/api/v1/console/scans/{scan_id} returns the check's current status:
Polling once a minute is enough for a check that takes several minutes.
A check carries these fields:
| Field | Meaning |
|---|---|
id | The check id (the scan_id from submission). |
domain_id | The Console domain the check ran on. |
status | pending, running, completed, failed, or cancelled. |
measurement_scope / measurement_label | full_benchmark / Full Check for every Console check. |
answers_total | Answers the check collects: one per prompt and platform. |
answers_completed / answers_failed | Answers collected so far, and answers that could not be collected. |
visibility_score | The domain's Visibility Score from this check, once completed. |
brand_coverage_pct | Brand Coverage from this check: the share of answers that mention the brand. |
citation_count | Answers that cite the domain. |
market_position | The domain's Market Position among its competitors, when competitors exist. |
started_at / completed_at / duration_seconds | When the check ran and how long it took. |
error_message | A short explanation written for customers, set when a check fails or is cancelled. |
selected_platforms | The platform keys the check ran on. |
customer_keywords | The prompts you sent, or null when SurfacedBy generated them. |
created_at | When the check was submitted. |
Step 6: Read the results
When the check completes, fetch what it measured, from the results_url in the scan.completed event or directly:
Every number comes from this check alone, not from earlier checks on the same domain. The response carries:
| Field | Meaning |
|---|---|
scan_id, domain_id, domain | The check and the domain it ran on. |
measurement_scope / measurement_label | full_benchmark / Full Check. |
completed_at | When the check finished. |
headline.visibility_score | The Visibility Score (0-100) the check produced. |
headline.brand_coverage_pct | Brand Coverage: the share of collected answers that mention the brand, cited or named. |
headline.ai_mentions | AI Mentions: how many collected answers mention the brand. |
headline.market_position | Market Position among the domain's competitors; null when the domain has no competitors to rank against. |
headline.prompts, headline.answers, headline.answers_failed | Prompts in the check, answers collected, and answers that could not be collected. |
platforms[] | One entry per AI platform: platform (key), name, status, prompts_tested, answers, answers_failed, cited (answers that cite your site), named (answers that name the brand without citing it), brand_coverage_pct, and avg_position (your average position among cited sources, null when no answer cited you). |
platforms[].status | complete when every answer came back, partial when some could not be collected, no_answers when the platform returned none. |
sources[] | Every third-party site the answers cited, most-mentioned first: domain, source_category (competitor, editorial, ugc, reference, institutional, company, your_brand, or other), mention_count (answers citing it), cited_with_you (whether any of those answers also mention you), and cited_with_you_count. |
prompts[] | Each prompt with prompt (its text) and presence, a map from platform key to how the brand showed up in that platform's answer: cited, named, considered (your page was among the sources the platform read, but the answer did not use it), missing, or null when the platform returned no answer for the prompt. |
Until the check completes, the endpoint returns 409 results_not_ready with the check's status. A check that ended as failed or cancelled has no results and returns the same 409. GET https://api.surfacedby.com/api/v1/console/domains/{domain_id} still returns the domain's latest headline metrics: latest_visibility_score, latest_brand_coverage_pct (Brand Coverage), latest_market_position (Market Position), last_scan_at, and scan_count.
Handling failures
A check that ends in failed is refunded in full automatically. The refund appears in the ledger (GET https://api.surfacedby.com/api/v1/console/credits/ledger) as a row with reason refund and the check's scan_id.
Cancelling a check
POST https://api.surfacedby.com/api/v1/console/scans/{scan_id}/cancel marks a pending or running check as cancelled. The response includes prior_status and refunded_credits:
- Cancelled while
pending: the full charge is refunded andrefunded_creditsequals it. - Cancelled while
running: credits stay debited andrefunded_creditsis0.
Cancelling a check that already ended returns 409.