SurfacedBySurfacedBy Docs

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.

Ask an AI:Open in ChatGPTOpen in Claude

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 with sk_test_. Send it in the X-API-Key header.

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:

curl -X POST https://api.surfacedby.com/api/v1/console/domains \
  -H "X-API-Key: sk_live_..." \
  -H "Idempotency-Key: 7c1e9a52-4b0d-4f3e-a8c6-2d9b1e0f5a37" \
  -H "Content-Type: application/json" \
  -d '{"domain": "yourbrand.com"}'

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:

curl -X POST https://api.surfacedby.com/api/v1/console/webhooks \
  -H "X-API-Key: sk_live_..." \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://yourapp.example.com/surfacedby/hook",
    "events": ["scan.completed", "scan.failed"]
  }'

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

curl -X POST https://api.surfacedby.com/api/v1/console/scans/preview \
  -H "X-API-Key: sk_live_..." \
  -H "Content-Type: application/json" \
  -d '{
    "domain_id": "DOMAIN_ID",
    "keywords": ["best crm for small teams", "crm with email tracking", "simple sales pipeline tool"],
    "platforms": ["chatgpt", "claude", "perplexity", "gemini"]
  }'

Request fields, shared with check submission:

FieldRequiredMeaning
domain_idYesThe id of one of your Console domains.
keywordsNoThe prompts to run, up to the per-check maximum in the credit model. Omit to let SurfacedBy generate 20 prompts for the domain.
platformsNoPlatform 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:

curl -X POST https://api.surfacedby.com/api/v1/console/scans \
  -H "X-API-Key: sk_live_..." \
  -H "Idempotency-Key: 9f8e2b1a-0c4d-4a7e-8b3f-5d1e2a3c6d7f" \
  -H "Content-Type: application/json" \
  -d '{
    "domain_id": "DOMAIN_ID",
    "keywords": ["best crm for small teams", "crm with email tracking", "simple sales pipeline tool"],
    "platforms": ["chatgpt", "claude", "perplexity", "gemini"]
  }'

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:

  • 402 with required_credits, current_balance, and a purchase_url when your balance is too low.
  • 409 scan_already_running when another check is pending or running for the same domain.
  • 404 domain_not_found when the domain_id is not one of your Console domains.
  • 422 validation_error for an unknown platform key or more prompts than the per-check maximum.
  • 422 platform_unavailable when a requested platform is paused right now. The unavailable_platforms field 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:

{
  "created_at": "2026-09-01T12:34:56.789012+00:00",
  "data": {
    "completed_at": "2026-09-01T12:33:40.123456+00:00",
    "domain": "yourbrand.com",
    "domain_id": "3f6c2a1e-8b4d-4c1a-9e2f-7a5b6c8d9e0f",
    "is_first_check": false,
    "measurement_label": "Full Check",
    "measurement_scope": "full_benchmark",
    "results_summary": {
      "brand_coverage_pct": 42.5,
      "duration_seconds": 318,
      "total_citations": 17
    },
    "results_url": "https://api.surfacedby.com/api/v1/console/scans/0d4e6f7a-1b2c-4d3e-8f9a-0b1c2d3e4f5a/results",
    "scan_id": "0d4e6f7a-1b2c-4d3e-8f9a-0b1c2d3e4f5a",
    "started_at": "2026-09-01T12:28:22.004411+00:00",
    "visibility_score": 58,
    "workspace_id": "a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d",
    "workspace_slug": "yourbrand"
  },
  "id": "5b0f3c9e-2d7a-4e61-9f0a-8c1d2e3f4a5b",
  "livemode": true,
  "type": "scan.completed"
}
  • results_url is where the check's results are served (Step 6). Fetch it with your API key.
  • is_first_check is true for the first completed check on the domain.
  • results_summary carries the check's Brand Coverage (brand_coverage_pct), total_citations, and duration_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:

curl https://api.surfacedby.com/api/v1/console/scans/SCAN_ID \
  -H "X-API-Key: sk_live_..."

Polling once a minute is enough for a check that takes several minutes.

A check carries these fields:

FieldMeaning
idThe check id (the scan_id from submission).
domain_idThe Console domain the check ran on.
statuspending, running, completed, failed, or cancelled.
measurement_scope / measurement_labelfull_benchmark / Full Check for every Console check.
answers_totalAnswers the check collects: one per prompt and platform.
answers_completed / answers_failedAnswers collected so far, and answers that could not be collected.
visibility_scoreThe domain's Visibility Score from this check, once completed.
brand_coverage_pctBrand Coverage from this check: the share of answers that mention the brand.
citation_countAnswers that cite the domain.
market_positionThe domain's Market Position among its competitors, when competitors exist.
started_at / completed_at / duration_secondsWhen the check ran and how long it took.
error_messageA short explanation written for customers, set when a check fails or is cancelled.
selected_platformsThe platform keys the check ran on.
customer_keywordsThe prompts you sent, or null when SurfacedBy generated them.
created_atWhen 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:

curl https://api.surfacedby.com/api/v1/console/scans/SCAN_ID/results \
  -H "X-API-Key: sk_live_..."

Every number comes from this check alone, not from earlier checks on the same domain. The response carries:

FieldMeaning
scan_id, domain_id, domainThe check and the domain it ran on.
measurement_scope / measurement_labelfull_benchmark / Full Check.
completed_atWhen the check finished.
headline.visibility_scoreThe Visibility Score (0-100) the check produced.
headline.brand_coverage_pctBrand Coverage: the share of collected answers that mention the brand, cited or named.
headline.ai_mentionsAI Mentions: how many collected answers mention the brand.
headline.market_positionMarket Position among the domain's competitors; null when the domain has no competitors to rank against.
headline.prompts, headline.answers, headline.answers_failedPrompts 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[].statuscomplete 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 and refunded_credits equals it.
  • Cancelled while running: credits stay debited and refunded_credits is 0.

Cancelling a check that already ended returns 409.

On this page