Every API here is available over the APIs.io API and to AI agents over MCP.
MCP server
One button, every client — Claude, Cursor, VS Code and the rest.
https://apis.io/mcp
Tools for apis
7 MCP tools reach this
find_apisBrowse and filter every API in the catalog.
get_api_artifactsOne API's artifacts, grouped by type.
get_openapiThe primary OpenAPI for this API.
find_similar_apisAPIs that look like this one.
apis_io_searchSTART HERE — APIs, providers and tags for one query, each with its total.
resolveTurn a domain, URL or GitHub org into the provider it belongs to.
find_cohortsEvery scored population of providers in the catalog.
All 92 tools →
Call it yourself
curl for this page
This API
curl "https://apis.io/api/v1/apis/ecomscout-com:ecomscout-com-stores-api"
All apis
curl "https://apis.io/api/v1/apis?limit=25"
Calls without X-API-Key need no key. Calls with it need a paid plan — get a key.
Get an API key
Free tier, no form to fill in. Signing in shares your email address with us — we
store it to create your key and to recognise you if you sign in with another
provider. See our Privacy Policy and
Terms.
A second provider on the same verified email joins the account you already have.
openapi: 3.2.0
info:
title: EcomScout Public Stores API
version: 0.7.0
description: Unified ecommerce intelligence API for coding agents, app builders, and VC/PE analysts. Stores, products, apps, analytics, changes, signals, scrapes — one JSON object per resource. Read /v1/llms.txt then /v1/examples before calling data endpoints. Only 2xx responses burn daily quota and plan buckets; 4xx do not. Store contacts (company, emails, phones) on GET /v1/stores/{domain} and GET /v1/stores/{domain}/contacts.
servers:
- url: https://api.ecomscout.com
security:
- apiKey: []
- bearer: []
tags:
- name: Stores
description: 'Merchant search, profile, compare, contacts. Subresources: traffic, products, apps, history, changes, contacts.'
paths:
/v1/stores:
get:
tags:
- Stores
summary: Search stores, or group the same cohort (UI breakdown)
description: 'Without groupBy: paginated store cards. With groupBy=country|vertical|industry|category: same filters, transformed into a grouped table (app store-list Breakdown). Use this — not /v1/analytics/countries — when the cohort is filtered (minVisits, includeApps, includeProducts, country regions, isDropshipper, revenue/rating bands). meta.mode is list or grouped.'
operationId: listStores
parameters:
- name: q
in: query
schema:
type: string
description: Domain substring
- name: vertical
in: query
schema:
type: string
- name: category
in: query
schema:
type: string
- name: industry
in: query
schema:
type: string
- name: country
in: query
schema:
type: string
description: Country names, ISO codes, or regions (Europe, EU, Nordics, DACH, Baltics, Benelux). Comma list. Overrides countryCode.
- name: countryCode
in: query
schema:
type: string
minLength: 2
maxLength: 2
description: ISO-2 alias for country when country is omitted.
- name: excludeCountries
in: query
schema:
type: string
description: Omit these countries / regions.
- name: excludeVerticals
in: query
schema:
type: string
- name: isDropshipper
in: query
schema:
type: boolean
- name: appSlug
in: query
schema:
type: string
description: Single app. Alias for includeApps with one slug.
- name: includeApps
in: query
schema:
type: string
description: Comma slugs, OR unless appsMatch=all.
- name: requireApps
in: query
schema:
type: string
description: Comma slugs, AND (must have all).
- name: excludeApps
in: query
schema:
type: string
- name: appsMatch
in: query
schema:
type: string
enum:
- any
- all
- name: removedApps
in: query
schema:
type: string
description: Comma slugs. Stores that uninstalled any of these inside the window (default last 30 days; appRemovedSince to widen, max 180 days). Different from excludeApps.
- name: installedApps
in: query
schema:
type: string
description: Comma slugs. Stores that newly installed any of these inside the window (appInstalledSince to widen).
- name: appRemovedSince
in: query
schema:
type: string
format: date
- name: appInstalledSince
in: query
schema:
type: string
format: date
- name: includeProducts
in: query
schema:
type: string
description: 'Pipe tokens: cat:Name|sub:Dog|type:Dog Beds|name:cooling mat'
- name: excludeProducts
in: query
schema:
type: string
- name: productMatch
in: query
schema:
type: string
enum:
- any
- all
- name: minVisits
in: query
schema:
type: integer
- name: maxVisits
in: query
schema:
type: integer
- name: minRevenue
in: query
schema:
type: number
- name: maxRevenue
in: query
schema:
type: number
- name: minRating
in: query
schema:
type: number
- name: groupBy
in: query
schema:
type: string
enum:
- country
- vertical
- industry
- category
description: Switch to grouped rows (UI breakdown). Same filters. Pagination is over groups.
- name: groupBy2
in: query
schema:
type: string
enum:
- country
- vertical
- industry
- category
description: Optional nested dimension. Requires groupBy. Must differ from groupBy.
- name: groupMetric
in: query
schema:
type: string
enum:
- mean
- median
- max
- min
default: mean
description: How metrics.* are aggregated. totals.* stay sums.
- name: sort
in: query
schema:
type: string
enum:
- visits
- revenue
- rating
- domain
- stores
- name: order
in: query
schema:
type: string
enum:
- asc
- desc
- $ref: '#/components/parameters/limit'
- $ref: '#/components/parameters/offset'
/v1/stores/compare:
get:
tags:
- Stores
summary: Side-by-side store cards (2–8 domains)
operationId: compareStores
parameters:
- name: domains
in: query
required: true
schema:
type: string
example: gruntstyle.com,titan.fitness,michaeltoddbeauty.com
/v1/stores/{domain}:
get:
tags:
- Stores
summary: Unified store profile
description: 'Includes `inventoryProbe` (StoreInventoryProbe or null): whether a Signal on this store could measure sales. Check `inventoryProbe.probable` before POST /v1/signals.'
operationId: getStore
parameters:
- $ref: '#/components/parameters/domain'
/v1/stores/{domain}/history:
get:
tags:
- Stores
summary: Daily store snapshots
operationId: getStoreHistory
parameters:
- $ref: '#/components/parameters/domain'
- name: limit
in: query
schema:
type: integer
default: 90
maximum: 365
/v1/stores/{domain}/traffic:
get:
tags:
- Stores
summary: Traffic series + latest SimilarWeb
operationId: getStoreTraffic
parameters:
- $ref: '#/components/parameters/domain'
- name: limit
in: query
schema:
type: integer
default: 24
maximum: 120
/v1/stores/{domain}/competitors:
get:
tags:
- Stores
summary: Ranked competitor stores (same list as the app's Competitors tab)
description: 'Up to 24 stores ranked by `score`: product-type overlap (`sharedTokens`), traffic proximity (`trafficRatio`) and country/region/vertical match. `match.geo` is the pass that found it (country → region → global). Similarity ranking, not market share. Counts as a store detail view.'
operationId: listStoreCompetitors
parameters:
- $ref: '#/components/parameters/domain'
- name: limit
in: query
schema:
type: integer
default: 12
minimum: 1
maximum: 24
responses:
'200':
description: Competitors
content:
application/json:
schema:
type: object
properties:
data:
type: array
items:
$ref: '#/components/schemas/CompetitorStore'
meta:
type: object
properties:
domain:
type: string
limit:
type: integer
count:
type: integer
/v1/stores/{domain}/inventory-probe:
get:
tags:
- Stores
summary: Stock-readability verdict + per-product tags
description: Scrape-time cart probe of the store's best sellers. `probable=true` only when `status=ok`; `capped` / `unlimited` / `blocked` mean a Signal cannot measure sales on this store. The same verdict is on GET /v1/stores/{domain} as `inventoryProbe`. 404 with hint when the store exists but was never probed.
operationId: getStoreInventoryProbe
parameters:
- $ref: '#/components/parameters/domain'
responses:
'200':
description: Verdict
content:
application/json:
schema:
type: object
properties:
data:
allOf:
- $ref: '#/components/schemas/StoreInventoryProbe'
- type: object
properties:
products:
type: array
items:
$ref: '#/components/schemas/ProductInventoryProbe'
/v1/stores/{domain}/contacts:
get:
tags:
- Stores
summary: Company info, emails, phones, LinkedIn
description: Same reveal as the app contact drawer. Empty arrays mean no observation. Counts toward contactReveals plan bucket. emails lists verified addresses first and drops bounced ones; emailStatus maps verified addresses to valid | likely; people = named people with a verified mailbox {name, title, department, email, status, linkedinUrl}, leaders first, up to 15; verifiedCount = size of emailStatus.
operationId: getStoreContacts
parameters:
- $ref: '#/components/parameters/domain'
- name: department
in: query
schema:
type: string
description: 'Comma list to narrow people: leadership, sales, marketing, ecommerce, engineering, operations, finance, people, support, design, other'
/v1/stores/{domain}/verify-contacts:
get:
tags:
- Stores
summary: Status of a contact verification run
description: status none | pending | running | done | error; lastCheckedAt = last automatic or manual pass. Free.
operationId: getVerifyContactsStatus
parameters:
- $ref: '#/components/parameters/domain'
post:
tags:
- Stores
summary: Find & verify contacts for one store
description: Queues a site re-scan, a founder web search and an SMTP mailbox check (no email is sent). Usually 1-3 minutes; poll GET, then GET /contacts. 1 contactReveals; returns deduped:true (free) while a run is pending or within 1 hour of one finishing. 429 verify_queue_full when the shared queue is busy.
operationId: requestVerifyContacts
parameters:
- $ref: '#/components/parameters/domain'
/v1/contacts:
get:
tags:
- Stores
summary: Contacts by domain query (alias)
operationId: getContactsByQuery
parameters:
- name: domain
in: query
required: true
schema:
type: string
example: gruntstyle.com
components:
schemas:
CompetitorStore:
type: object
description: One ranked competitor. Similarity ranking (score), not market share.
properties:
domain:
type: string
url:
type: string
industry:
type: string
vertical:
type: string
category:
type: string
countryName:
type: string
monthlyRevenueUsdHigh:
type: number
description: Modeled high band, USD
monthlyVisits:
type: integer
ecomScoutRating:
type: number
ecomScoutGrade:
type: string
productCount:
type: integer
visitsChangePct:
type:
- number
- 'null'
trafficRatio:
type: number
description: Competitor visits ÷ seed store visits
tokenOverlap:
type: integer
sharedTokens:
type: array
items:
type: string
match:
type: object
properties:
country:
type: boolean
region:
type: boolean
industry:
type: boolean
vertical:
type: boolean
category:
type: boolean
geo:
type: string
enum:
- country
- region
- global
score:
type: number
ProductInventoryProbe:
type: object
properties:
handle:
type: string
title:
type: string
status:
type: string
enum:
- ok
- capped
- unlimited
- blocked
- no_inventory
- no_products
- unknown
attempted:
type: integer
measurable:
type: integer
capped:
type: integer
unlimited:
type: integer
failed:
type: integer
checkedAt:
type: string
format: date-time
StoreInventoryProbe:
type: object
description: Scrape-time stock-readability verdict. `probable` is true only for status=ok. capped / unlimited / blocked = a Signal cannot measure sales here. null on the store profile = never probed.
properties:
status:
type: string
enum:
- ok
- capped
- unlimited
- blocked
- no_inventory
- no_products
probable:
type: boolean
method:
type: string
enum:
- json
- cart_direct
- cart_proxy
- catalog_json
- none
attempted:
type: integer
description: Best-seller variants probed
measurable:
type: integer
description: Variants with a real stock number
capped:
type: integer
description: Variants that echoed the cart limit
unlimited:
type: integer
description: Variants with no stock tracking
failed:
type: integer
cartQtyCap:
type:
- integer
- 'null'
description: Detected per-order limit when capped
checkedAt:
type: string
format: date-time
fresh:
type: boolean
description: false when older than 14 days
reason:
type: string
description: One sentence to quote to the user
required:
- status
- probable
- method
- attempted
- measurable
- checkedAt
- fresh
- reason
parameters:
offset:
name: offset
in: query
schema:
type: integer
default: 0
domain:
name: domain
in: path
required: true
schema:
type: string
description: Bare domain or URL. gruntstyle.com and https://www.gruntstyle.com both work.
limit:
name: limit
in: query
schema:
type: integer
default: 20
maximum: 100
securitySchemes:
apiKey:
type: apiKey
in: header
name: X-Api-Key
bearer:
type: http
scheme: bearer