Every API here is available over the APIs.io API and to AI agents over MCP.
openapi: 3.2.0
info:
title: SerpKite SERP Monitors API
version: 1.12.4
description: 'Public search API (api.serpkite.com). Google results as clean JSON or Markdown
for AI agents, plus a Google Custom Search–compatible endpoint. Official SDKs:
`serpkite` on npm and PyPI, `github.com/serpkite/serpkite-go`. **Auth:** `Authorization: Bearer skt_live_…`. A bare `Authorization: skt_live_…`
(no scheme) is also accepted, for MCP hosts that can''t add one. GET requests
may pass `?api_key=` instead. `/customsearch/v1` also accepts Google''s `?key=`. **Bodies:** every vertical accepts `POST` with a JSON object or `GET` with the
same parameters in the query string (only `GET /v1/search` is spelled out
here). Unknown parameters are rejected with 400, and so is a parameter the
endpoint would ignore (e.g. `time` on `/v1/scholar`, `ll` on `/v1/search`). To run many queries at once,
use `POST /v1/batches`. **Formats:** `format=json` (default) returns the schemas below. `format=markdown` returns `text/markdown` for every vertical. `format=compact` and `fields=` return a reduced JSON object (same top-level
keys, fewer item fields, `meta` always kept), so required item fields may be
absent there. **Responses** share one envelope: `request` (the normalised request),
`results` (the vertical''s primary list), vertical-specific extras and `meta`. All keys are snake_case. **Credits** are decimal numbers (e.g. `0.5`). Failed, empty and blocked
responses are refunded. Every billed response carries `X-Credits-Used`,
`X-Credits-Remaining`, `X-Cost-USD`, `X-Cache` (`HIT`|`MISS`), `X-Latency-Ms`,
`X-Tokens-Estimate` and `X-Request-Id`. **Errors** share one body: `{"error":{"code","message","request_id"}}`. Upstream failures (`upstream_error`, `upstream_blocked`, `upstream_timeout`)
are `503` with `Retry-After`, never billed and safe to retry. The API never
returns `502` or `504`. `429` also carries `Retry-After`: `rate_limited` is
the per-second limit; `daily_limit_reached` is the free daily limit of an
account that never bought a pack, which resets at 00:00 UTC (the message
names the limit and the billing page; buying any pack removes it). `403 account_restricted` means the account''s free use is paused because it
appears to be one of several accounts run by the same person (same device
or network, or requests in step with other accounts) or uses a disposable
email address; buying any pack lifts it. **CORS:** `Access-Control-Allow-Origin: *` without credentials; the `X-*`
headers above are exposed, so a browser playground can call the API with a key. The keyless `/playground/search` is the exception: it only answers serpkite.com
and app.serpkite.com (other `Origin`s get 403). Credit table: search/news/images/videos/maps/places/shopping/scholar/patents/
webpage/customsearch = 1 per page; place details = 1; `num=100` depth bundle = 7; reviews = 1 per 10;
autocomplete = 0.5; `include_content` = +1 per fetched page;
batches ×0.5 (from 2026-11-23 a batch depth request is 0.5 per page with
results, without the bundle cap: `num=100` = 5); cache hit via `max_age` ×0.5. `engine=consensus` (search only) =
the sum of one page per provider that returned results. **AI** (Claude writes, OpenAI''s Decisions API runs typed checks; a failed
model step is never billed): an answer (`include_answer` on `/v1/search` and
`/v1/news`, the model part of `/v1/answer`) = +2 over the result page, +4
when it read pages (`include_content`, `/v1/answer` depth=standard);
`/v1/extract` `summary` = +4 and `output_schema` = +4 per page;
`/v1/research` = lite 30, standard 75, deep 200. Responses that used a
model carry `meta.llm`. **AI assistants** (`/v1/chatgpt`, `/v1/gemini`, `/v1/copilot`,
`/v1/ai_mode`, `/v1/brave_ai`): a `prompt` sent to the public assistant
as a logged-out user; the response is its answer
(`text`, `markdown`) with the `sources` it cited or listed,
`search_queries`, `products` and `places` where the assistant shows them,
and `mentions` for the brands and domains in `track`.'
servers:
- url: https://api.serpkite.com
- url: http://localhost:8080
security:
- bearer: []
tags:
- name: Monitors
paths:
/v1/monitors:
post:
summary: Create a monitor — a saved /v1/search or /v1/news request (new results) or a…
operationId: createMonitor
description: 'Runs hourly to monthly (`interval` or `interval_seconds`, default daily); the
first run is due right away (unless `active: false`). With `endpoint: webpage`
the monitor watches `url` and reports the page (Markdown, `change: new|changed`)
whenever its content changes (1 credit per check). Each run is billed like the
search it makes (1 credit per 10 results, `num: 100` is 7; empty and failed runs
are free) on the monitor''s key: the key that created it, or last changed or ran
it, under that key''s limits and the account''s spend cap. Only the fields below
are kept; any other search field is a `400`. Results among the
top `num` not seen in earlier runs are POSTed as a `monitor.results` webhook when
the monitor has a `webhook_url`, and are always listed in its run history
(`GET /v1/monitors/{id}/runs`, results kept 24 hours). At most 100 monitors per
account (403 `forbidden`). A monitor pauses itself after 10 failed runs in a row
(search errors, insufficient credits or webhook failures) or when its key is
revoked; set `active: true` to resume.
With an AI assistant `endpoint` (chatgpt, gemini, copilot, ai_mode,
brave_ai) the monitor asks `prompt` every run and reports every answer
(AI visibility is tracked over time): `text`, `markdown`, `sources`,
`new_sources` (links not seen in earlier runs), `search_queries`,
`products` and, with `track`, `mentions`. Each run costs the endpoint''s
price.'
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/MonitorCreateRequest'
example:
q: ai agents
endpoint: news
interval: hourly
name: agents news
webhook_url: https://example.com/hooks/serpkite
responses:
'201':
description: Created
content:
application/json:
schema:
$ref: '#/components/schemas/Monitor'
'400':
$ref: '#/components/responses/Error'
'401':
$ref: '#/components/responses/Error'
'403':
$ref: '#/components/responses/Error'
'429':
$ref: '#/components/responses/Error'
tags:
- Monitors
get:
summary: List the account's monitors
operationId: listMonitors
responses:
'200':
description: OK
content:
application/json:
schema:
$ref: '#/components/schemas/MonitorList'
'401':
$ref: '#/components/responses/Error'
'429':
$ref: '#/components/responses/Error'
tags:
- Monitors
/v1/monitors/{id}:
parameters:
- name: id
in: path
required: true
schema:
type: string
format: uuid
get:
summary: Get a monitor
operationId: getMonitor
responses:
'200':
description: OK
content:
application/json:
schema:
$ref: '#/components/schemas/Monitor'
'401':
$ref: '#/components/responses/Error'
'404':
$ref: '#/components/responses/Error'
'429':
$ref: '#/components/responses/Error'
tags:
- Monitors
patch:
summary: 'Update a monitor: name, interval, webhook_url (empty string removes it)…'
operationId: updateMonitor
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/MonitorUpdateRequest'
example:
active: false
interval: daily
responses:
'200':
description: OK
content:
application/json:
schema:
$ref: '#/components/schemas/Monitor'
'400':
$ref: '#/components/responses/Error'
'401':
$ref: '#/components/responses/Error'
'404':
$ref: '#/components/responses/Error'
'429':
$ref: '#/components/responses/Error'
tags:
- Monitors
delete:
summary: Delete a monitor
operationId: deleteMonitor
responses:
'204':
description: Deleted
'401':
$ref: '#/components/responses/Error'
'404':
$ref: '#/components/responses/Error'
'429':
$ref: '#/components/responses/Error'
tags:
- Monitors
/v1/monitors/{id}/runs:
parameters:
- name: id
in: path
required: true
schema:
type: string
format: uuid
- name: limit
in: query
schema:
type: integer
minimum: 1
maximum: 100
default: 20
- name: before
in: query
description: '`next_before` of the previous page'
schema:
type: string
format: uuid
get:
summary: A monitor's run history, newest first (runs are kept 30 days, their results 24…
operationId: listMonitorRuns
responses:
'200':
description: OK
content:
application/json:
schema:
$ref: '#/components/schemas/MonitorRunList'
'400':
$ref: '#/components/responses/Error'
'401':
$ref: '#/components/responses/Error'
'404':
$ref: '#/components/responses/Error'
'429':
$ref: '#/components/responses/Error'
tags:
- Monitors
/v1/monitors/{id}/run:
parameters:
- name: id
in: path
required: true
schema:
type: string
format: uuid
post:
summary: Run a monitor now (it becomes due at the next scheduler poll, within about 30…
description: 'The monitor must be active. A paused monitor returns 409; resume it with
PATCH {"active": true} first (resuming also makes it due immediately).
The monitor is bound to the calling key, which this and later runs are billed to.'
operationId: runMonitor
responses:
'202':
description: Scheduled
content:
application/json:
schema:
$ref: '#/components/schemas/Monitor'
'401':
$ref: '#/components/responses/Error'
'404':
$ref: '#/components/responses/Error'
'409':
$ref: '#/components/responses/Error'
'429':
$ref: '#/components/responses/Error'
tags:
- Monitors
webhooks:
crawl.completed:
post:
operationId: crawlCompletedWebhook
summary: A crawl finished (completed, failed or canceled)
description: 'Sent to the crawl''s `webhook_url` (or the account webhook). Signed like batch
webhooks: `X-SerpKite-Event: crawl.completed`, `X-SerpKite-Delivery` (the task id),
`X-SerpKite-Timestamp` and `X-SerpKite-Signature: v1=.")>`. Retried with backoff until a 2xx.'
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/TaskCompletedEvent'
responses:
'200':
description: Any 2xx acknowledges the delivery
tags:
- Monitors
research.completed:
post:
operationId: researchCompletedWebhook
summary: A research task finished (completed, failed or canceled)
description: 'Sent to the task''s `webhook_url` (or the account webhook), signed like
`crawl.completed` with `X-SerpKite-Event: research.completed`. `result` is the
ResearchResult.'
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/TaskCompletedEvent'
responses:
'200':
description: Any 2xx acknowledges the delivery
tags:
- Monitors
monitor.results:
post:
operationId: monitorResultsWebhook
summary: A monitor run found results it had not seen before
description: 'Sent to the monitor''s `webhook_url` (when it has one) only when a run finds new results (the first
run sends everything it found, with `first_run: true`). Same signature headers as
the other webhooks, with `X-SerpKite-Event: monitor.results` and
`X-SerpKite-Delivery: `. Tried up to 3 times per run; results that could
not be delivered stay new for the next run (and are in the run history either way).'
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/MonitorResultsEvent'
responses:
'200':
description: Any 2xx acknowledges the delivery
tags:
- Monitors
components:
schemas:
DomainList:
description: 'Domains as an array or a comma-separated string, at most 20: a host (`example.com`, subdomains match), a host with a path prefix (`github.com/org`) or a TLD (`.gov`).'
oneOf:
- type: string
examples:
- github.com,en.wikipedia.org
- type: array
maxItems: 20
items:
type: string
examples:
- - github.com/serpkite
- .gov
TrackList:
type: array
maxItems: 20
items:
type: string
maxLength: 100
description: Brands ("Acme") and domains ("acme.com", subdomains match) to check the answer for; each comes back in `mentions`
MonitorSearch:
type: object
description: 'The saved request a monitor reruns: a search (q and options), for webpage monitors url (and country), or for AI assistant monitors prompt (with country, language, web_search and track)'
properties:
q:
type: string
url:
type: string
prompt:
type: string
description: AI assistant monitors
web_search:
type: boolean
description: endpoint=chatgpt
track:
type: array
items:
type: string
country:
type: string
language:
type: string
location:
type: string
time:
type: string
enum:
- hour
- day
- week
- month
- year
num:
type: integer
device:
type: string
safe:
type: string
include_domains:
type: array
items:
type: string
exclude_domains:
type: array
items:
type: string
engine:
type: array
items:
type: string
MonitorList:
type: object
required:
- results
properties:
results:
type: array
items:
$ref: '#/components/schemas/Monitor'
TaskCompletedEvent:
type: object
description: Body of `crawl.completed` and `research.completed`
required:
- event
- id
- kind
- status
- created_at
- credits_used
properties:
event:
type: string
enum:
- crawl.completed
- research.completed
id:
type: string
format: uuid
kind:
type: string
enum:
- crawl
- research
status:
$ref: '#/components/schemas/TaskStatus'
created_at:
type: string
format: date-time
completed_at:
type:
- string
- 'null'
format: date-time
credits_used:
type: number
poll_url:
type: string
description: Where the task (and its full result) can be fetched for 24 hours
result:
description: 'The CrawlResult or ResearchResult (null when the task failed without one, or when it is larger than 4 MB: see result_omitted)'
result_omitted:
type: boolean
description: Set when the result was too large for the webhook; GET poll_url for it
error:
type: object
properties:
code:
type: string
message:
type: string
MonitorRunList:
type: object
required:
- results
- next_before
properties:
results:
type: array
items:
$ref: '#/components/schemas/MonitorRun'
next_before:
type:
- string
- 'null'
format: uuid
description: Pass as `before` for the next page; null on the last page
Monitor:
type: object
required:
- id
- name
- endpoint
- request
- interval_seconds
- webhook_url
- active
- next_run_at
- runs
- consecutive_failures
- credits_used
- created_at
properties:
id:
type: string
format: uuid
name:
type: string
endpoint:
type: string
enum:
- search
- news
- webpage
- chatgpt
- gemini
- copilot
- ai_mode
- brave_ai
request:
$ref: '#/components/schemas/MonitorSearch'
metadata:
type:
- object
- 'null'
additionalProperties: true
interval_seconds:
type: integer
webhook_url:
type:
- string
- 'null'
description: Null when new results are only kept in the run history
active:
type: boolean
next_run_at:
type: string
format: date-time
last_run_at:
type:
- string
- 'null'
format: date-time
last_status:
type:
- string
- 'null'
enum:
- ok
- error
- webhook_failed
- paused
- null
last_error:
type:
- string
- 'null'
last_new_results:
type: integer
description: New results found by the last run
runs:
type: integer
consecutive_failures:
type: integer
description: Failed runs in a row; the monitor pauses itself at 10
credits_used:
type: number
description: Total over all runs
created_at:
type: string
format: date-time
EngineParam:
description: 'Which search providers may answer. `google` (the default) is Google only; Google requests are retried internally before failing. `auto` allows falling back to other enabled providers, in route order, when Google is blocked or times out. A single other provider (`brave`) or a list (`["google","brave"]`, or `google,brave` in a query string) restricts the request to those providers. `meta.engine` always names the provider that answered and credits follow that provider''s price. A key with "Allow fallback to other search engines" on treats a request without `engine` as `auto`. `consensus` (`/v1/search` only; 400 elsewhere, and it can''t be combined with other names) queries several independent search indexes in parallel (at most one Bing-backed provider, so Yahoo or DuckDuckGo never count as agreeing with Bing), merges the results by URL and ranks them by how many providers returned each one. Each result lists its `sources`; `meta.engine` is `consensus` and `meta.route` lists every provider attempt. Up to three providers answer: a provider that fails or comes back empty hands its turn to the next one in the route. It stops once `num` unique results are in hand or after a few seconds, and costs the sum of one page at each provider that returned results (providers that failed, came back empty or were never started are free). `yandex` serves `/v1/search` from Yandex web search (no `time` filter or `safe: active`: a request naming it gets a 400 with them) and `/v1/maps`, `/v1/places` and `/v1/place` from Yandex Maps, where the deployment enables it (otherwise a 503 `unavailable`, not charged); with `auto` it is the last fallback there.'
oneOf:
- type: string
default: google
examples:
- google
- auto
- consensus
- brave
- google,brave
- type: array
items:
$ref: '#/components/schemas/Provider'
minItems: 1
Provider:
type: string
description: A search provider (docs/providers). Which verticals each serves depends on the deployment.
enum:
- google
- brave
- bing
- yahoo
- duckduckgo
- mojeek
- wikipedia
- yandex
MonitorUpdateRequest:
type: object
description: Only the fields sent change. Search fields are merged into the saved search; a changed search (or endpoint) makes the next run report every result as new.
properties:
name:
type: string
maxLength: 200
interval:
type: string
enum:
- hourly
- daily
- weekly
interval_seconds:
type: integer
minimum: 3600
maximum: 2592000
webhook_url:
type: string
description: A webhook URL, or an empty string to remove it
active:
type: boolean
description: true resumes a paused monitor (due now, failure streak cleared)
metadata:
type:
- object
- 'null'
additionalProperties: true
description: Replaces the metadata (null clears it)
endpoint:
type: string
enum:
- search
- news
- webpage
- chatgpt
- gemini
- copilot
- ai_mode
- brave_ai
description: Switching between a search, a page and an AI prompt drops the other kinds' fields
q:
type: string
minLength: 1
maxLength: 2048
prompt:
type: string
minLength: 1
maxLength: 4000
web_search:
type: boolean
track:
$ref: '#/components/schemas/TrackList'
url:
type: string
format: uri
description: endpoint=webpage only
country:
type: string
language:
type: string
location:
type: string
time:
type: string
enum:
- hour
- day
- week
- month
- year
num:
type: integer
minimum: 1
maximum: 100
device:
type: string
enum:
- desktop
- mobile
safe:
type: string
enum:
- active
- 'off'
include_domains:
$ref: '#/components/schemas/DomainList'
exclude_domains:
$ref: '#/components/schemas/DomainList'
engine:
$ref: '#/components/schemas/EngineParam'
MonitorRun:
type: object
required:
- id
- status
- new_results
- credits_used
- created_at
properties:
id:
type: string
format: uuid
status:
type: string
enum:
- ok
- error
- webhook_failed
- paused
error:
type:
- string
- 'null'
new_results:
type: integer
results:
type:
- array
- 'null'
description: The new results (OrganicResult-shaped for search, NewsResult for news, MonitorPageChange for webpage, one MonitorAnswer per run for AI assistants); null when there were none or after 24 hours
items:
type: object
additionalProperties: true
credits_used:
type: number
webhook_status:
type:
- string
- 'null'
enum:
- delivered
- failed
- none
- null
description: none = the monitor has no webhook; null = nothing to send
created_at:
type: string
format: date-time
MonitorCreateRequest:
type: object
description: Search and news monitors need `q`; webpage monitors need `url` and take only `country` besides; AI assistant monitors need `prompt` and take `country`, `language` (not chatgpt), `web_search` (chatgpt) and `track`
properties:
q:
type: string
minLength: 1
maxLength: 2048
url:
type: string
format: uri
description: 'endpoint=webpage: the page to watch for content changes'
prompt:
type: string
minLength: 1
maxLength: 4000
description: 'AI assistant monitors: the prompt asked every run'
web_search:
type: boolean
description: endpoint=chatgpt
track:
$ref: '#/components/schemas/TrackList'
endpoint:
type: string
enum:
- search
- news
- webpage
- chatgpt
- gemini
- copilot
- ai_mode
- brave_ai
default: search
metadata:
type: object
additionalProperties: true
description: Your own JSON object (at most 2 KB), echoed on the monitor and in its webhooks
name:
type: string
maxLength: 200
interval:
type: string
enum:
- hourly
- daily
- weekly
default: daily
interval_seconds:
type: integer
minimum: 3600
maximum: 2592000
description: Overrides interval
webhook_url:
type: string
format: uri
description: Receives signed `monitor.results` events. Without one, read new results from the run history
active:
type: boolean
default: true
description: false creates the monitor paused
country:
type: string
language:
type: string
location:
type: string
time:
type: string
enum:
- hour
- day
- week
- month
- year
num:
type: integer
minimum: 1
maximum: 100
device:
type: string
enum:
- desktop
- mobile
safe:
type: string
enum:
- active
- 'off'
include_domains:
$ref: '#/components/schemas/DomainList'
exclude_domains:
$ref: '#/components/schemas/DomainList'
engine:
$ref: '#/components/schemas/EngineParam'
TaskStatus:
type: string
enum:
- queued
- running
- completed
- failed
- canceled
Error:
type: object
required:
- error
properties:
error:
type: object
required:
- code
- message
properties:
code:
type: string
description: '403: spend_cap_reached, key_limit_reached, account_restricted, forbidden. 409/422: idempotency_key_reused. 422: target_unavailable (the URL you asked for failed: its site answered 404, its domain doesn''t resolve; resending won''t help), no_pages. 429: rate_limited, daily_limit_reached. 503: upstream_error, upstream_blocked, upstream_timeout, unavailable (the API never sends 502 or 504)'
examples:
- unauthorized
- invalid_request
- insufficient_credits
- rate_limited
- daily_limit_reached
- spend_cap_reached
- key_limit_reached
- account_restricted
- idempotency_key_reused
- forbidden
- not_found
- upstream_error
- upstream_blocked
- upstream_timeout
- unavailable
- internal
message:
type: string
request_id:
type: string
MonitorResultsEvent:
type: object
description: Body of `monitor.results`
required:
- event
- monitor_id
- run_id
- name
- endpoint
- first_run
- run_at
- new_results
- credits_used
properties:
event:
type: string
const: monitor.results
monitor_id:
type: string
format: uuid
run_id:
type: string
format: uuid
description: Also the X-SerpKite-Delivery header; use it to deduplicate retries
name:
type: string
endpoint:
type: string
enum:
- search
- news
- webpage
- chatgpt
- gemini
- copilot
- ai_mode
- brave_ai
q:
type: string
description: The saved query (empty for webpage monitors
absent for AI assistant monitors): null
url:
type: string
description: webpage monitors only
prompt:
type: string
description: AI assistant monitors only (instead of q)
metadata:
type:
- object
- 'null'
additionalProperties: true
description: The monitor's metadata
first_run:
type: boolean
description: The first run since the monitor was created or its search changed
run_at:
type: string
format: date-time
new_results:
type: array
description: Results not seen in earlier runs (OrganicResult-shaped for search, NewsResult for news, MonitorPageChange for webpage); for AI assistant monitors the run's answer (one MonitorAnswer, sent every run)
items:
type: object
additionalProperties: true
credits_used:
type: number
description: This run's price
responses:
Error:
description: Error
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
securitySchemes:
bearer:
type: http
scheme: bearer
oauth:
type: oauth2
description: OAuth 2.1 for the remote MCP server only (authorization code + PKCE S256; app-api.serpkite.com is the authorization server).
flows:
authorizationCode:
authorizationUrl: https://app-api.serpkite.com/oauth/authorize
tokenUrl: https://app-api.serpkite.com/oauth/token
refreshUrl: https://app-api.serpkite.com/oauth/token
scopes:
mcp: Use the SerpKite MCP tools, billed to your account
offline_access: Stay connected with refresh tokens