Every API here is available over the APIs.io API and to AI agents over MCP.
openapi: 3.2.0
info:
title: Searlo Domain API
description: Live search results as clean JSON. The search endpoints return Google results — web, images, news, video, shopping, maps, scholar, patents and the full results page with ads, People Also Ask and AI Overviews — plus AI answers (ChatGPT, Copilot, Gemini), rank checks, web data (scrape, crawl, map, extract) and account endpoints. Get a free API key with 3,000 credits (no card required) at https://dashboard.searlo.tech. Not affiliated with or endorsed by Google LLC or any other search engine. All trademarks belong to their owners.
version: 1.2.0
contact:
name: Searlo
url: https://searlo.tech
email: hello@searlo.tech
license:
name: Proprietary API — see https://searlo.tech/terms
servers:
- url: https://api.searlo.tech/api/v1
description: Production
security:
- ApiKeyAuth: []
- BearerAuth: []
tags:
- name: Domain
description: 'CURRENTLY UNAVAILABLE (coming soon): these endpoints return 403 FEATURE_NOT_AVAILABLE (not retryable, not charged) until their keyword data source is re-enabled. Domain-level SEO reports — keyword footprint, top pages, competitors, keyword gap and overview. Every row states its provenance: `google_ads_site_seed` (Google Ads'' keyword association, no position, priced as one flat discovery operation) or `serp_verified` (a live SERP Searlo ran, priced per keyword per depth). /domain/top-pages and /domain/competitors always run verified.'
paths:
/domain/keywords:
get:
tags:
- Domain
summary: Domain keyword footprint
description: 'Keywords Google Ads associates with a domain''s crawled content, demand-ordered, with search volume, CPC and competition. Unverified by default — `position` is null and `source` reads `google_ads_site_seed` — for a flat discovery-operation cost regardless of row count. Pass `verify=true` to also run a live SERP on up to `verify_limit` (max 10 per call) of the returned keywords; those rows switch to `source: serp_verified` with a real `position`, and the unverified remainder stays in the response, clearly marked. Verifying costs a SERP per keyword, priced by `depth` (10-100, in pages of 10).'
parameters:
- name: domain
in: query
required: true
description: Domain to look up, e.g. example.com.
schema:
type: string
minLength: 3
maxLength: 253
- $ref: '#/components/parameters/DomainGl'
- $ref: '#/components/parameters/DomainHl'
- name: limit
in: query
schema:
type: integer
minimum: 1
maximum: 1000
default: 100
- name: min_volume
in: query
description: Floor on search_volume; rows below it are dropped.
schema:
type: integer
minimum: 0
default: 0
- $ref: '#/components/parameters/DomainVerify'
- $ref: '#/components/parameters/DomainVerifyLimit'
- $ref: '#/components/parameters/DomainDevice'
- $ref: '#/components/parameters/DomainLocation'
- $ref: '#/components/parameters/DomainDepth'
responses:
'200':
$ref: '#/components/responses/DomainKeywordsResult'
'400':
$ref: '#/components/responses/BadRequest'
'401':
$ref: '#/components/responses/Unauthorized'
'402':
$ref: '#/components/responses/InsufficientCredits'
'429':
$ref: '#/components/responses/RateLimited'
operationId: getDomainKeywords
x-operation-id-source: derived
/domain/top-pages:
get:
tags:
- Domain
summary: Domain top pages
description: Pages that actually rank, grouped from verified positions on a domain's keyword footprint. Always verified — there is no `verify` parameter, because a page cannot be a "top page" for a keyword nobody looked up. Coverage is bounded by `verify_limit` (max 10 keywords per call), not by the domain's whole footprint; the response's own `meta` says so.
parameters:
- name: domain
in: query
required: true
schema:
type: string
minLength: 3
maxLength: 253
- $ref: '#/components/parameters/DomainGl'
- $ref: '#/components/parameters/DomainHl'
- name: limit
in: query
description: Pages to return.
schema:
type: integer
minimum: 1
maximum: 200
default: 50
- name: min_volume
in: query
schema:
type: integer
minimum: 0
default: 0
- $ref: '#/components/parameters/DomainVerifyLimit'
- $ref: '#/components/parameters/DomainDevice'
- $ref: '#/components/parameters/DomainLocation'
- $ref: '#/components/parameters/DomainDepth'
responses:
'200':
$ref: '#/components/responses/DomainTopPagesResult'
'400':
$ref: '#/components/responses/BadRequest'
'401':
$ref: '#/components/responses/Unauthorized'
'402':
$ref: '#/components/responses/InsufficientCredits'
'429':
$ref: '#/components/responses/RateLimited'
operationId: getDomainTopPages
x-operation-id-source: derived
/domain/competitors:
get:
tags:
- Domain
summary: Domain competitors
description: Domains that co-occur on the SERPs verified for this domain's keywords, ranked by shared keyword count. `overlap` is the share of sampled SERPs each rival appeared on (0-1). Always verified, like /domain/top-pages — no `verify` parameter.
parameters:
- name: domain
in: query
required: true
schema:
type: string
minLength: 3
maxLength: 253
- $ref: '#/components/parameters/DomainGl'
- $ref: '#/components/parameters/DomainHl'
- name: limit
in: query
schema:
type: integer
minimum: 1
maximum: 200
default: 50
- name: min_volume
in: query
schema:
type: integer
minimum: 0
default: 0
- $ref: '#/components/parameters/DomainVerifyLimit'
- $ref: '#/components/parameters/DomainDevice'
- $ref: '#/components/parameters/DomainLocation'
- $ref: '#/components/parameters/DomainDepth'
responses:
'200':
$ref: '#/components/responses/DomainCompetitorsResult'
'400':
$ref: '#/components/responses/BadRequest'
'401':
$ref: '#/components/responses/Unauthorized'
'402':
$ref: '#/components/responses/InsufficientCredits'
'429':
$ref: '#/components/responses/RateLimited'
operationId: getDomainCompetitors
x-operation-id-source: derived
/domain/gaps:
get:
tags:
- Domain
summary: Domain keyword gap (query form)
description: Keywords up to 10 competitor domains are associated with that the target is not, ranked by how many competitors share each term and by search volume. Unverified by default — 2 credits per domain queried (target plus each competitor); pass `verify=true` to also measure the target's live position on up to `verify_limit` of the returned gap keywords. Use the GET form for a small competitor list; POST accepts the same fields as a JSON body.
parameters:
- name: target
in: query
required: true
schema:
type: string
minLength: 3
maxLength: 253
- name: competitors
in: query
required: true
description: '1-10 competitor domains. A comma-separated string or repeated query values are both accepted.
'
schema:
oneOf:
- type: array
items:
type: string
minLength: 3
maxLength: 253
minItems: 1
maxItems: 10
- type: string
- $ref: '#/components/parameters/DomainGl'
- $ref: '#/components/parameters/DomainHl'
- name: limit
in: query
description: Keywords pulled per domain before diffing.
schema:
type: integer
minimum: 1
maximum: 1000
default: 200
- name: result_limit
in: query
description: Gap rows returned, after diffing and sorting.
schema:
type: integer
minimum: 1
maximum: 1000
default: 100
- name: min_volume
in: query
schema:
type: integer
minimum: 0
default: 0
- $ref: '#/components/parameters/DomainVerify'
- $ref: '#/components/parameters/DomainVerifyLimit'
- $ref: '#/components/parameters/DomainDevice'
- $ref: '#/components/parameters/DomainLocation'
- $ref: '#/components/parameters/DomainDepth'
responses:
'200':
$ref: '#/components/responses/DomainGapsResult'
'400':
$ref: '#/components/responses/BadRequest'
'401':
$ref: '#/components/responses/Unauthorized'
'402':
$ref: '#/components/responses/InsufficientCredits'
'429':
$ref: '#/components/responses/RateLimited'
operationId: getDomainGaps
x-operation-id-source: derived
post:
tags:
- Domain
summary: Domain keyword gap (body form)
description: Same report as GET /domain/gaps, with `target` and 1-10 `competitors` sent as a JSON body instead of query parameters — the more ergonomic form once the competitor list is longer than a couple of domains.
requestBody:
required: true
content:
application/json:
schema:
type: object
required:
- target
- competitors
properties:
target:
type: string
minLength: 3
maxLength: 253
competitors:
type: array
items:
type: string
minLength: 3
maxLength: 253
minItems: 1
maxItems: 10
gl:
type: string
minLength: 2
maxLength: 2
default: us
hl:
type: string
minLength: 2
maxLength: 5
default: en
limit:
type: integer
minimum: 1
maximum: 1000
default: 200
result_limit:
type: integer
minimum: 1
maximum: 1000
default: 100
min_volume:
type: integer
minimum: 0
default: 0
verify:
type: boolean
default: false
verify_limit:
type: integer
minimum: 1
maximum: 10
default: 10
device:
type: string
enum:
- desktop
- mobile
default: desktop
location:
type: string
maxLength: 300
depth:
type: integer
minimum: 10
maximum: 100
default: 100
responses:
'200':
$ref: '#/components/responses/DomainGapsResult'
'400':
$ref: '#/components/responses/BadRequest'
'401':
$ref: '#/components/responses/Unauthorized'
'402':
$ref: '#/components/responses/InsufficientCredits'
'429':
$ref: '#/components/responses/RateLimited'
operationId: postDomainGaps
x-operation-id-source: derived
/domain/overview:
get:
tags:
- Domain
summary: Domain overview
description: A domain's keyword footprint, top topics and top keywords, rolled up from the same discovery call as /domain/keywords — 2 credits flat. `organic_visibility` (keywords measured, keywords ranking, top-3/10/100 counts, average position) is `null` unless `verify=true`, since it requires live SERPs rather than modelled Ads data.
parameters:
- name: domain
in: query
required: true
schema:
type: string
minLength: 3
maxLength: 253
- $ref: '#/components/parameters/DomainGl'
- $ref: '#/components/parameters/DomainHl'
- name: limit
in: query
schema:
type: integer
minimum: 50
maximum: 1000
default: 200
- $ref: '#/components/parameters/DomainVerify'
- $ref: '#/components/parameters/DomainVerifyLimit'
- $ref: '#/components/parameters/DomainDevice'
- $ref: '#/components/parameters/DomainLocation'
- $ref: '#/components/parameters/DomainDepth'
responses:
'200':
$ref: '#/components/responses/DomainOverviewResult'
'400':
$ref: '#/components/responses/BadRequest'
'401':
$ref: '#/components/responses/Unauthorized'
'402':
$ref: '#/components/responses/InsufficientCredits'
'429':
$ref: '#/components/responses/RateLimited'
operationId: getDomainOverview
x-operation-id-source: derived
components:
responses:
DomainTopPagesResult:
description: Domain top pages
content:
application/json:
schema:
$ref: '#/components/schemas/DomainTopPagesResponse'
DomainOverviewResult:
description: Domain overview
content:
application/json:
schema:
$ref: '#/components/schemas/DomainOverviewResponse'
DomainCompetitorsResult:
description: Domain competitors
content:
application/json:
schema:
$ref: '#/components/schemas/DomainCompetitorsResponse'
DomainKeywordsResult:
description: Domain keyword footprint
content:
application/json:
schema:
$ref: '#/components/schemas/DomainKeywordsResponse'
BadRequest:
description: 'Invalid request (code VALIDATION_ERROR; errors lists the fields). Search endpoints also answer 400 PAGE_OUT_OF_RANGE (maxPage) and DATE_FILTER_UNAVAILABLE, with the name in errorCode. Never charged.
'
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
InsufficientCredits:
description: 'Account balance too low to complete the request (code INSUFFICIENT_CREDITS). details gives required, available and shortfall; topUpUrl links to buying more.
'
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
RateLimited:
description: 'The account''s rate limit (code 4201), the API key''s own (code 4202) or the cap on searches in flight (code 4203, errorCode CONCURRENCY_LIMIT, Retry-After 1, X-Concurrency-Limit carries the cap). Wait Retry-After seconds. See also AT_CAPACITY / BUSY / VERIFIER_BUSY, a full lane rather than a limit reached. Never charged.
'
headers:
Retry-After:
schema:
type: integer
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
Unauthorized:
description: Missing, invalid or expired API key (code UNAUTHORIZED)
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
DomainGapsResult:
description: Domain keyword gap
content:
application/json:
schema:
$ref: '#/components/schemas/DomainGapsResponse'
parameters:
DomainVerify:
name: verify
in: query
description: 'Run a live SERP per keyword to add measured positions. Defaults to false. Not offered on /domain/top-pages or /domain/competitors, which always verify.
'
schema:
type: boolean
default: false
DomainHl:
name: hl
in: query
description: Interface/result language. Defaults to en.
schema:
type: string
minLength: 2
maxLength: 5
default: en
DomainGl:
name: gl
in: query
description: Country code for geolocation. Defaults to us.
schema:
type: string
minLength: 2
maxLength: 2
default: us
DomainDevice:
name: device
in: query
description: Device to verify positions on.
schema:
type: string
enum:
- desktop
- mobile
default: desktop
DomainVerifyLimit:
name: verify_limit
in: query
description: 'Maximum keywords to verify with a live SERP in this call. Capped at 10 regardless of endpoint — call again to cover more of a longer list.
'
schema:
type: integer
minimum: 1
maximum: 10
default: 10
DomainLocation:
name: location
in: query
description: A country for the verifying SERP, by name, code or uule, applied as gl. A city or state returns 400 CITY_LOCATION_UNAVAILABLE.
schema:
type: string
maxLength: 300
DomainDepth:
name: depth
in: query
description: 'Result depth to check per verified keyword (10-100), rounded up internally to the next page of 10. Higher depth costs more credits per keyword verified.
'
schema:
type: integer
minimum: 10
maximum: 100
default: 100
schemas:
ErrorResponse:
type: object
properties:
success:
type: boolean
example: false
code:
description: 'Stable machine-readable name of the failure. Strings: VALIDATION_ERROR, INVALID_JSON, INVALID_IDEMPOTENCY_KEY, CITY_LOCATION_UNAVAILABLE, UNKNOWN_LOCATION, INVALID_DOMAIN, UNAUTHORIZED, INSUFFICIENT_CREDITS, FORBIDDEN, FEATURE_NOT_AVAILABLE, API_KEY_SCOPE_FORBIDDEN, NOT_FOUND, IDEMPOTENCY_REQUEST_IN_PROGRESS, PAYLOAD_TOO_LARGE, QUERY_NOT_SERVED, COUNTRY_UNAVAILABLE, IDEMPOTENCY_KEY_REUSED, TARGET_NOT_ALLOWED, AT_CAPACITY, BUSY, VERIFIER_BUSY, INTERNAL_ERROR, UPSTREAM_UNAVAILABLE, UPSTREAM_RATE_LIMITED, WARMING, UPSTREAM_TIMEOUT, CREDIT_SYSTEM_UNAVAILABLE, REVIEWS_WITHHELD, SERVICE_UNAVAILABLE, UPSTREAM_FAILED. The rate-limit 429s use numbers: 4201 (account), 4202 (API key), 4203 (searches in flight). Some 4xx carry their name in errorCode instead (see errorCode). No error is charged.
'
oneOf:
- type: string
- type: integer
example: CITY_LOCATION_UNAVAILABLE
message:
type: string
description: For people; may be reworded. Branch on code.
retryable:
type: boolean
description: Whether the same request can succeed later.
retryAfterSec:
type: integer
description: Seconds to wait before a retry (also the Retry-After header).
errors:
type: array
description: Validation errors, one per field.
items:
type: object
properties:
field:
type: string
message:
type: string
suggestions:
type: array
items:
type: string
description: 'UNKNOWN_LOCATION: close matches.'
location:
type: string
description: 'CITY_LOCATION_UNAVAILABLE: the place the request named.'
errorCode:
type: string
description: 'The name on errors that carry it here instead of in code, so read code and fall back to errorCode: PAGE_OUT_OF_RANGE (with maxPage), DATE_FILTER_UNAVAILABLE, INVALID_REQUEST, PLACE_NOT_FOUND, TARGET_REFUSED (with targetStatus), TARGET_UNREACHABLE (with targetError), INSUFFICIENT_CREDITS on POST /extract. Also set with code on 4203 (CONCURRENCY_LIMIT), FEATURE_NOT_AVAILABLE, TARGET_NOT_ALLOWED, REVIEWS_WITHHELD and every 503.
'
retryAfter:
type: integer
description: Seconds to wait, on 429 4201 to 4203 and on a retryable 503.
maxPage:
type: integer
description: 'PAGE_OUT_OF_RANGE: the last page that can be served at this limit.'
targetStatus:
type: integer
description: 'TARGET_REFUSED: the site''s own HTTP status.'
targetError:
type: string
description: 'TARGET_UNREACHABLE: why the site could not be fetched.'
details:
type: object
description: 'INSUFFICIENT_CREDITS: required, available and shortfall.'
additionalProperties:
type: number
topUpUrl:
type: string
description: 'INSUFFICIENT_CREDITS: a link to buy more credits.'
DomainTopPagesResponse:
type: object
properties:
schema:
type: string
example: domain-top-pages.v1
results:
type: array
items:
type: object
properties:
url:
type: string
keywords:
type: integer
total_volume:
type: integer
best_position:
type:
- integer
- 'null'
avg_position:
type: number
top_keyword:
type: string
credits:
type: object
properties:
used:
type: number
meta:
type: object
description: Carries pages_found and a note on verify_limit-bounded coverage.
additionalProperties: true
DomainKeywordRow:
type: object
properties:
keyword:
type: string
search_volume:
type:
- integer
- 'null'
cpc:
type:
- number
- 'null'
competition:
type:
- string
- 'null'
parent_topic:
type:
- string
- 'null'
position:
type:
- integer
- 'null'
description: Set only when verified is true.
url:
type:
- string
- 'null'
page_title:
type: string
description: Present on verified rows.
serp_features:
type: array
items:
type: string
description: Present on verified rows.
competitors_above:
type: array
items:
type: string
description: Domains ranking above this one on the verifying SERP. Present on verified rows.
verified:
type: boolean
verify_failed:
type: boolean
description: True when verification was requested but the SERP check failed.
source:
type: string
enum:
- google_ads_site_seed
- serp_verified
DomainCompetitorsResponse:
type: object
properties:
schema:
type: string
example: domain-competitors.v1
results:
type: array
items:
type: object
properties:
domain:
type: string
common_keywords:
type: integer
overlap:
type: number
description: Share of sampled SERPs this domain appeared on, 0-1.
common_volume:
type: integer
avg_position:
type: number
credits:
type: object
properties:
used:
type: number
meta:
type: object
description: Carries domain, serps_sampled, competitors_found, gl, hl and a note.
additionalProperties: true
DomainOverviewResponse:
type: object
properties:
schema:
type: string
example: domain-overview.v1
result:
type: object
properties:
domain:
type: string
gl:
type: string
hl:
type: string
keyword_footprint:
type: integer
keywords_with_volume:
type: integer
total_search_volume:
type: integer
avg_cpc:
type:
- number
- 'null'
top_topics:
type: array
items:
type: object
properties:
topic:
type: string
keywords:
type: integer
volume:
type: integer
top_keywords:
type: array
items:
type: object
properties:
keyword:
type: string
search_volume:
type:
- integer
- 'null'
position:
type: integer
description: Present only on rows a verified call measured.
organic_visibility:
type:
- object
- 'null'
description: Null unless the request that built this overview passed verify=true.
properties:
keywords_measured:
type: integer
keywords_ranking:
type: integer
top_3:
type: integer
top_10:
type: integer
top_100:
type: integer
avg_position:
type: number
credits:
type: object
properties:
used:
type: number
meta:
type: object
additionalProperties: true
DomainGapRow:
type: object
properties:
keyword:
type: string
search_volume:
type:
- integer
- 'null'
cpc:
type:
- number
- 'null'
competition:
type:
- string
- 'null'
parent_topic:
type:
- string
- 'null'
competitors:
type: array
items:
type: string
description: Which of the named competitor domains this keyword came from.
competitor_coverage:
type: number
description: Share of named competitors associated with this keyword, 0-1.
position:
type:
- integer
- 'null'
description: Set only when verify=true and this row was checked.
url:
type:
- string
- 'null'
verified:
type: boolean
source:
type: string
enum:
- google_ads_site_seed
- serp_verified
DomainGapsResponse:
type: object
properties:
schema:
type: string
example: domain-gaps.v1
results:
type: array
items:
$ref: '#/components/schemas/DomainGapRow'
credits:
type: object
properties:
used:
type: number
meta:
type: object
description: Carries target, competitors, target_keywords, gaps_found, returned, gl, hl, verified, verified_count, verify_failures and a note.
additionalProperties: true
DomainKeywordsResponse:
type: object
properties:
schema:
type: string
example: domain-keywords.v1
results:
type: array
items:
$ref: '#/components/schemas/DomainKeywordRow'
credits:
type: object
properties:
used:
type: number
meta:
type: object
description: Carries domain, gl, hl, verified, verified_count, verify_failures, unverified_count, verify_cap and a human-readable note.
additionalProperties: true
securitySchemes:
ApiKeyAuth:
type: apiKey
in: header
name: x-api-key
description: 'API key from your Searlo dashboard, e.g. sk_.... Get a free key with 3,000 credits at https://dashboard.searlo.tech (no card required).
'
BearerAuth:
type: http
scheme: bearer
description: 'The same API key, sent as Authorization: Bearer sk_.... A bearer token that does not start with sk_ is read as a dashboard login token.
'
CseKeyParam:
type: apiKey
in: query
name: key
description: 'The Searlo API key as the key query parameter, where Google''s Custom Search JSON API takes its key. Used by /customsearch/v1 only.
'
externalDocs:
description: Full documentation
url: https://searlo.tech/docs