Every request is authenticated with an API key. You can only target domains connected to your spysay account.
Base URL
https://spysay.com/v1
API keys
Create and manage keys in the dashboard under Channels, API and MCP, or with the API keys endpoints. A key starts with spy_live_ and is shown once, when you create it: spysay stores only a hash, so a lost key can't be recovered, only replaced.
A key is either account-wide or scoped to one domain. A scoped key can read and update its own domain under /v1/domains/{hostname} and nothing else: any other endpoint answers 403 requires_unscoped_key, and another domain 403 domain_not_owned. Use an account-wide key for businesses, questions, reports and keys.
Send the key in either header:
curl https://spysay.com/v1/domains \
-H "x-spysay-api-key: <API_KEY>"
# Or as a Bearer token
curl https://spysay.com/v1/domains \
-H "Authorization: Bearer <API_KEY>"
import { createClient } from "@spysay/sdk";
const spysay = createClient({ baseUrl: "https://spysay.com", token: process.env.SPYSAY_API_KEY! });
// The SDK sends `token` as a Bearer token.
const domains = await spysay.domains.list();
When a request carries a key, the key decides the account, even in a browser that's signed in to the dashboard. A key that's wrong or revoked is refused with 401 invalid_api_key; spysay never falls back to the signed-in session.
WARNING
Keep keys server-side. Never ship them in browser code or commit them to git. To show analytics in a browser, use a public stats token.
Credits
Your plan sets how many markets and questions you can track, and includes credits each month. Work that costs something is paid for from your credit balance, first the plan's monthly credits, then any you top up. Reading data is free.
| Uses credits | Free |
|---|---|
Renders that actually render (miss, or fresh=true) |
Cache hits (edge_hit, hit) |
| Each page a cache purge re-renders | Listing and retrieving domains, audits, prompts and reports |
| Each page an audit crawls | Analytics |
| Each provider answer to a prompt | Stats and visibility reports |
When the balance can't cover a request, it fails with 402 insufficient_credits, and the error includes credits_required and credits_balance. Background work reserves its credits when it starts, so an audit or a scheduled prompt run can't overdraw your balance halfway through.
Errors
| Status | Type | Code | Meaning |
|---|---|---|---|
401 |
authentication_error |
missing_api_key |
No key in the request |
401 |
authentication_error |
invalid_api_key |
Key is invalid or revoked |
403 |
permission_error |
requires_unscoped_key |
A domain-scoped key called an account-wide endpoint |
403 |
permission_error |
domain_not_owned |
A domain-scoped key called another domain |
402 |
billing_error |
insufficient_credits |
The credit balance can't cover the request |
See Error Codes for the full list.