Getting started
ShopLens API
Data on Shopify stores: store metrics, best-selling products, similar shops, ads on Meta, Google and TikTok, and the people who work at each store's company. Monitors watch shops for you and send what changed to a webhook. Use it from your code through the REST API or from an AI assistant through the MCP server.
Everything is JSON over HTTPS. Reads use GET. POST, PATCH and DELETE reveal a contact and manage monitors. The OpenAPI 3.1 spec is the source of truth for every field.
The API is versioned in the path. New fields and endpoints ship in v1, so ignore fields you don't know. Removing or renaming a field would ship as /api/v2, and v1 would keep working.
Getting started
Authentication
Every REST endpoint needs an API key, sent in the Authorization header as Bearer <key>. Create keys in the ShopLens dashboard. Keep them on your server, never in browser code.
A missing, invalid or revoked key answers 401. A revoked key stops working at once. Only the documentation (/llms.txt, /llms-full.txt, /api/v1/openapi.json) and /api/v1/health are public.
The MCP server does not take API keys. It signs you in with OAuth instead.
Getting started
Credits
Calls are paid with the credits of your plan. The REST API and the MCP server spend the same credits, and each endpoint below shows what it costs next to its path. See the pricing page for plans.
- Only successful calls (
200or201) spend credits. Errors, and a shop that is still being looked up (202), are free. - A call costs the same whether its data was already stored or fetched for it. Revealing a person again costs the reveal again, so keep the contact.
- With fewer credits than a call can cost, it answers
402and doesn't run, so your balance never goes below zero. A search needs itslimitin credits available (25 by default) and is charged only for the shops it returns: with a low balance, lowerlimit. - Plan credits renew each billing period. Credits from recharges don't expire and are spent after your plan's.
Getting started
Rate limits
Each account has a limit of requests per minute that depends on its plan: 120 on Starter, 600 on Growth and 1,800 on Scale. All your API keys and the MCP server share it.
- Above the limit, calls answer
429with aRetry-Afterheader in seconds. They don't run and spend no credits. - The count is approximate. Spread bursts out instead of aiming at the exact limit.
Getting started
Identifying a shop
Shops are addressed by domain, not by ID. In paths, use the bare domain: /shops/example-shop.com works, /shops/https://example-shop.com does not, because its slashes are read as extra path segments.
A leading www. is ignored, and the shop's .myshopify.com domain also works. The q search parameter does accept a full URL.
In responses, a shop's id is a Shopify global ID, gid://shopify/Shop/<number>. A best-selling product's id is gid://shopify/Product/<number>, or null for shops whose products have not been refreshed yet.
Getting started
Shops not indexed yet
When you ask Get a shop for a domain ShopLens has never seen, it queues the domain and answers 202 Accepted. A background job checks that the domain is a public Shopify store and enriches it, which takes a few minutes. The Retry-After header carries the same number of seconds as retryAfter. Poll with that delay, not right away.
200: the shop is ready.202: still being looked up. Try again afterRetry-Afterseconds.404: the domain is not a public Shopify store (not Shopify, password-protected, or unreachable for more than a day). This is final.
Search, ads and people only work with shops that already exist, and answer 404 for unknown ones.
Getting started
Pagination
Lists are paginated with cursors. A paginated response has hasNextPage and nextCursor. While hasNextPage is true, repeat the same request with cursor=<nextCursor>.
- Cursors are opaque: don't parse or build them. There are no offsets.
limitis 1 to 50, 25 by default. Ads on one platform have nolimit: the page size is the platform's.- Keep the filters, sort and order the same while paginating.
Getting started
Data conventions
- Null means unknown. Most fields are nullable because a shop may not be fully analysed yet. A missing value is not zero.
- Money is a decimal string, never a float.
estimatedMonthlySalesandestimatedMonthlyAppSpendare{ "value": "125000.00", "currency": "USD" }, or null when there is no estimate. - Dates: a shop's
createdAtis a calendar date,YYYY-MM-DD, with no time. Other timestamps, such as an ad'sfirstShownAt, are ISO 8601 date-times in UTC.
Getting started
Errors
Errors have the shape { "error": "message" }. Invalid parameters answer 400 with an issues list, where path names the invalid field.
Without enough credits for a call, it answers 402. Above your plan's rate limit, it answers 429.
Calling a path with a method it doesn't support answers 405 with an Allow header.
Endpoints
Search shops
GET/api/v1/shops1 credit per shop returned
Filters and ranks the shops ShopLens has indexed. Returns summaries. Call Get a shop for best sellers and product types.
- Range filters are inclusive and named
min<Metric>/max<Metric>. The metrics areEstimatedMonthlyVisits,EstimatedMonthlySales(USD),ProductsCount,VariantsCountandCollectionsCount. minCreatedAtandmaxCreatedAtfilter by the date the shop was created. With either one set, shops with no known creation date are left out.- Shops without a value for the
sortfield come last. WithcreatedAt, those are shops with no known creation date. Withoutsortthe order is stable but arbitrary. - A cursor only works with the
sortandorderit was issued for, otherwise the API answers400. Keep the filters the same while paginating.
Query parameters
qstringoptional
Text to search in the shop name or domain.
countrystringoptional
ISO 3166-1 alpha-2 code, such as US or ES.
categoryenumoptional
One of:
APPARELSHOESJEWELRY_WATCHESBAGS_ACCESSORIESBEAUTY_PERSONAL_CAREHEALTH_SUPPLEMENTSSPORTS_FITNESSHOME_DECORFURNITUREKITCHEN_DININGGARDEN_OUTDOORTOOLS_HARDWAREFOOD_BEVERAGESPETSBABY_KIDSTOYS_GAMESELECTRONICSCOMPUTERS_GAMINGBOOKS_MEDIAARTS_CRAFTS_COLLECTIBLESOFFICE_STATIONERYAUTOMOTIVEBUSINESS_INDUSTRIALDIGITAL_PRODUCTSGIFTS_PARTYSMOKING_VAPINGADULTWEAPONS_HUNTINGMASS_MERCHANTSOTHERstatusenumoptional
ACTIVE,PASSWORD,CLOSED,NOT_FOUNDorUNKNOWN.shopifyPlusbooleanoptional
true: only Shopify Plus shops. false: only shops known not to be on Plus.
minEstimatedMonthlyVisitsnumberoptional
Minimum estimated monthly visits, inclusive.
maxEstimatedMonthlyVisitsnumberoptional
Maximum estimated monthly visits, inclusive.
minEstimatedMonthlySalesnumberoptional
Minimum estimated monthly sales in USD, inclusive.
maxEstimatedMonthlySalesnumberoptional
Maximum estimated monthly sales in USD, inclusive.
minProductsCountnumberoptional
Minimum number of products, inclusive.
maxProductsCountnumberoptional
Maximum number of products, inclusive.
minVariantsCountnumberoptional
Minimum number of product variants, inclusive.
maxVariantsCountnumberoptional
Maximum number of product variants, inclusive.
minCollectionsCountnumberoptional
Minimum number of collections, inclusive.
maxCollectionsCountnumberoptional
Maximum number of collections, inclusive.
minCreatedAtdateoptional
Earliest date the shop was created,
YYYY-MM-DD, inclusive.maxCreatedAtdateoptional
Latest date the shop was created,
YYYY-MM-DD, inclusive.sortenumoptional
estimatedMonthlyVisits,estimatedMonthlySales,productsCount,variantsCount,collectionsCountorcreatedAt(the date the shop was created).orderenumoptional
desc(default, highest or newest first) orasc.limitintegeroptional
Number of results, 1 to 50. Defaults to 25.
cursorstringoptional
nextCursorfrom the previous response, as is.
Responses
200A page of shops
shopsobject[]
19 fields
idstring
gid://shopify/Shop/<number>createdAtstring | null
Date the shop was created,
YYYY-MM-DD.namestring | null
domainstring | null
Primary domain.
myshopifyDomainstring
countryCodestring | null
ISO 3166-1 alpha-2.
citystring | null
regionstring | null
State or province.
currencystring | null
The shop's base currency, ISO 4217.
primaryLanguagestring | null
Language code, such as
en.categoryenum | null
One of the values of the
categoryfilter in Search shops.statusenum | null
ACTIVE,PASSWORD,CLOSED,NOT_FOUNDorUNKNOWN.productsCountinteger | null
variantsCountinteger | null
collectionsCountinteger | null
estimatedMonthlyVisitsinteger | null
estimatedMonthlySalesobject | null
Null when there is no estimate.
2 fields
valuestring
Decimal amount, such as
125000.00.currencystring
Always
USD.
estimatedMonthlyAppSpendobject | null
Monthly spend on Shopify apps. Null when there is no estimate.
2 fields
valuestring
Decimal amount, such as
125000.00.currencystring
Always
USD.
isShopifyPlusboolean | null
true: on Shopify Plus. false: known not to be on Plus. null: no signal.
hasNextPageboolean
nextCursorstring | null
Pass it as
cursorfor the next page. Null on the last page.
400A parameter is invalid
errorstring
Always
Invalid request.issuesobject[]
2 fields
pathstring
The invalid parameter, such as
limit.messagestring
401Missing, invalid or revoked API key
errorstring
What went wrong.
402Not enough credits for this call
errorstring
What went wrong.
Endpoints
Get a shop
GET/api/v1/shops/{domain}1 credit
Returns the shop, its best-selling products (topProducts, best first), its product types (productTypes, most products first) and the shops most similar to it (similarShops). It is the only endpoint that looks up domains ShopLens has not seen yet. See Shops not indexed yet.
similarShopslists the domains of the 5 to 10 shops most similar to this one, to find competitors or alternatives. It is empty until the comparison has been computed. Pass any of them back to this endpoint.taxonomyCategory, on best sellers and product types, is a path from the Shopify Product Taxonomy in English, such asApparel & Accessories > Clothing.merchantProductTypeis the type as the merchant wrote it. It is free text, so two shops can name the same thing differently.
Path parameters
domainstringrequired
Bare shop domain, such as
example-shop.com, withouthttps://or slashes. A leadingwww.is ignored and the.myshopify.comdomain also works.
Responses
200The shop. If the domain redirects to another store's main domain, that store is returned
idstring
gid://shopify/Shop/<number>createdAtstring | null
Date the shop was created,
YYYY-MM-DD.namestring | null
domainstring | null
Primary domain.
myshopifyDomainstring
countryCodestring | null
ISO 3166-1 alpha-2.
citystring | null
regionstring | null
State or province.
currencystring | null
The shop's base currency, ISO 4217.
primaryLanguagestring | null
Language code, such as
en.categoryenum | null
One of the values of the
categoryfilter in Search shops.statusenum | null
ACTIVE,PASSWORD,CLOSED,NOT_FOUNDorUNKNOWN.productsCountinteger | null
variantsCountinteger | null
collectionsCountinteger | null
estimatedMonthlyVisitsinteger | null
estimatedMonthlySalesobject | null
Null when there is no estimate.
2 fields
valuestring
Decimal amount, such as
125000.00.currencystring
Always
USD.
estimatedMonthlyAppSpendobject | null
Monthly spend on Shopify apps. Null when there is no estimate.
2 fields
valuestring
Decimal amount, such as
125000.00.currencystring
Always
USD.
isShopifyPlusboolean | null
true: on Shopify Plus. false: known not to be on Plus. null: no signal.
languagesstring[]
Published languages.
shopifyThemestring | null
Only set for themes from the Shopify Theme Store.
emailsstring[]
phonesstring[]
similarShopsstring[]
Domains of the 5 to 10 most similar shops. Empty when not computed yet.
topProductsobject[]
Best sellers, best first.
10 fields
idstring | null
gid://shopify/Product/<number>. Null for shops whose products have not been refreshed yet.rankinteger
1 is the best seller.
titlestring
handlestring
merchantProductTypestring | null
taxonomyCategorystring | null
shopUrlstring | null
featuredImagestring | null
pricestring | null
Lowest variant price, as a decimal string.
currencystring | null
productTypesobject[]
Most products first.
3 fields
merchantProductTypestring | null
taxonomyCategorystring | null
productsCountinteger
202Not indexed yet, being looked up
statusstring
Always
pending.messagestring
retryAfterinteger
Seconds to wait before asking again, same as the
Retry-Afterheader.
400A parameter is invalid
errorstring
Always
Invalid request.issuesobject[]
2 fields
pathstring
The invalid parameter, such as
limit.messagestring
401Missing, invalid or revoked API key
errorstring
What went wrong.
402Not enough credits for this call
errorstring
What went wrong.
404No public Shopify store at this domain
errorstring
What went wrong.
Endpoints
Get a shop's ads
GET/api/v1/shops/{domain}/ads40 credits
Returns, for each of meta, google and tiktok, the advertiser found for the shop, a link to its public ad library, the total number of ads and its 20 most recent ads, newest first (fewer if the advertiser has fewer).
- Meta and TikTok are searched by name, and only an advertiser whose name matches the shop is accepted. When none matches,
advertiseris null andtotalis 0. ShopLens would rather return no advertiser than another company's ads. totalis the count the platform reports, approximate on Meta and Google, and null when unknown. To page through every ad, use List every ad on one platform.placementssays where an ad runs inside the platform, such asfacebookorinstagram. Only Meta fills it.
Path parameters
domainstringrequired
Bare shop domain, such as
example-shop.com, withouthttps://or slashes. A leadingwww.is ignored and the.myshopify.comdomain also works.
Responses
200Ads per platform
platformsobject
1 field
meta, google, tiktokobject | null
One key per platform, each with these fields.
4 fields
advertiserobject | null
Null when no advertiser matched the shop.
2 fields
idstring | null
namestring | null
libraryUrlstring | null
All of the advertiser's ads in the ad library.
totalinteger | null
Null when unknown.
adsobject[]
Up to 20, newest first.
14 fields
idstring
The platform's own ad ID.
placementsstring[]
Empty outside Meta.
formatstring | null
image,videoortext.titlestring | null
textstring | null
ctastring | null
imageUrlstring | null
videoUrlstring | null
landingUrlstring | null
libraryUrlstring | null
This ad in the ad library.
isActiveboolean | null
firstShownAtstring | null
ISO 8601 date-time, UTC.
lastShownAtstring | null
ISO 8601 date-time, UTC.
daysRunninginteger | null
Calendar days (UTC) the ad has run, counting the first and last day: from
firstShownAttolastShownAt, or to today for an active ad with nolastShownAt. Null when the dates are unknown.
400A parameter is invalid
errorstring
Always
Invalid request.issuesobject[]
2 fields
pathstring
The invalid parameter, such as
limit.messagestring
401Missing, invalid or revoked API key
errorstring
What went wrong.
402Not enough credits for this call
errorstring
What went wrong.
404No shop with this domain
errorstring
What went wrong.
Endpoints
List every ad on one platform
GET/api/v1/shops/{domain}/ads/{platform}40 credits per page
Pages through all of the shop's ads on one platform, active and inactive, with the same advertiser, libraryUrl and total as Get a shop's ads. Use that endpoint when the 20 most recent ads are enough.
- Each page is read from the platform's ad library when you ask for it, so its size is the platform's, about 10 to 30 ads, and there is no
limit. Meta returns the newest first. Google and TikTok use their own default order. - The filters are applied by the platform's library, so each platform supports a different set: Meta all but
placement, GooglestartDate,endDate,format,countryandplacement, TikTok none. A filter the platform does not support answers400instead of being ignored. - A cursor only works with the platform, advertiser and filters it was issued for, otherwise the API answers
400. Keep the same filters while paginating. - When no advertiser matched the shop,
advertiseris null andadsis empty. If the platform fails, the API answers502. Retry with the same cursor.
Path parameters
domainstringrequired
Bare shop domain, such as
example-shop.com, withouthttps://or slashes. A leadingwww.is ignored and the.myshopify.comdomain also works.platformenumrequired
meta,googleortiktok.
Query parameters
startDatedateoptional
Only ads shown on or after this date,
YYYY-MM-DD. Meta and Google.endDatedateoptional
Only ads shown on or before this date,
YYYY-MM-DD. Meta and Google.formatenumoptional
image,videoortext. Meta and Google.countrystringoptional
Only ads shown in this country, ISO 3166-1 alpha-2 such as
US. Meta and Google.statusenumoptional
all(default),activeorinactive. Meta only.languagestringoptional
Ad language, ISO 639-1 such as
en. Meta only.placementenumoptional
search,youtube,shopping,mapsorplay. Google only.sortenumoptional
recent(default, newest first) orimpressions(most impressions first). Meta only.cursorstringoptional
nextCursorfrom the previous response, as is.
Responses
200A page of ads
advertiserobject | null
Null when no advertiser matched the shop.
2 fields
idstring | null
namestring | null
libraryUrlstring | null
All of the advertiser's ads in the ad library.
totalinteger | null
Null when unknown.
adsobject[]
One page, in the platform's order.
14 fields
idstring
The platform's own ad ID.
placementsstring[]
Empty outside Meta.
formatstring | null
image,videoortext.titlestring | null
textstring | null
ctastring | null
imageUrlstring | null
videoUrlstring | null
landingUrlstring | null
libraryUrlstring | null
This ad in the ad library.
isActiveboolean | null
firstShownAtstring | null
ISO 8601 date-time, UTC.
lastShownAtstring | null
ISO 8601 date-time, UTC.
daysRunninginteger | null
Calendar days (UTC) the ad has run, counting the first and last day: from
firstShownAttolastShownAt, or to today for an active ad with nolastShownAt. Null when the dates are unknown.
hasNextPageboolean
nextCursorstring | null
Pass it as
cursorfor the next page. Null on the last page.
400A parameter is invalid
errorstring
Always
Invalid request.issuesobject[]
2 fields
pathstring
The invalid parameter, such as
limit.messagestring
401Missing, invalid or revoked API key
errorstring
What went wrong.
402Not enough credits for this call
errorstring
What went wrong.
404No shop with this domain
errorstring
What went wrong.
502The platform failed. Retry with the same cursor
errorstring
What went wrong.
Endpoints
List people at a shop
GET/api/v1/shops/{domain}/people5 credits
Lists people who work at the shop's company, with masked last names. hasEmail and hasPhone say what is available to reveal.
- The list never includes contacts, not even for people already revealed.
email,phoneandlinkedinUrlonly come from Reveal a person's contact. totalis the number of people reported for the company, or null.
Path parameters
domainstringrequired
Bare shop domain, such as
example-shop.com, withouthttps://or slashes. A leadingwww.is ignored and the.myshopify.comdomain also works.
Query parameters
limitintegeroptional
Number of results, 1 to 50. Defaults to 25.
cursorstringoptional
nextCursorfrom the previous response, as is.
Responses
200A page of people
totalinteger | null
Null when unknown.
peopleobject[]
6 fields
idstring
Use it to reveal the contact.
firstNamestring | null
lastNamestring | null
Masked, such as
Sm***h, until the contact is revealed.titlestring | null
Job title.
hasEmailboolean | null
Whether an email is available to reveal.
hasPhoneboolean | null
Whether a direct phone is available to reveal.
hasNextPageboolean
nextCursorstring | null
Pass it as
cursorfor the next page. Null on the last page.
400A parameter is invalid
errorstring
Always
Invalid request.issuesobject[]
2 fields
pathstring
The invalid parameter, such as
limit.messagestring
401Missing, invalid or revoked API key
errorstring
What went wrong.
402Not enough credits for this call
errorstring
What went wrong.
404No shop with this domain
errorstring
What went wrong.
Endpoints
Reveal a person's contact
POST/api/v1/shops/{domain}/people/{id}/reveal25 credits, 150 with a phone
Reveals one person's work email and LinkedIn URL and returns the person with email, phone and linkedinUrl, each null when not available. phone is only returned with ?phone=true, which also reveals the phone number. Without it, phone is always null. It is the only way to get a contact. Call it only when a user asks for that person's contact, never over a whole list.
- Use the
idfrom the people list. Only people listed for that shop can be revealed. - The contact is stored, so revealing the same person again returns it right away, but it costs the same credits (150 with
phone=truewhen a phone is returned). Keep the contact instead of revealing it again. Asking for the phone later, for someone revealed without it, does one more lookup. - Phone lookups often fail at the provider. On a first reveal the email and LinkedIn are then returned without the phone, so check whether
phoneis null.
Path parameters
domainstringrequired
Bare shop domain, such as
example-shop.com, withouthttps://or slashes. A leadingwww.is ignored and the.myshopify.comdomain also works.idstringrequired
The person's
idfrom the people list.
Query parameters
phonebooleanoptional
trueto also reveal the phone number. Without it the phone is never returned. Defaults tofalse.
Responses
200The person with their revealed contact
idstring
Use it to reveal the contact.
firstNamestring | null
lastNamestring | null
Unmasked.
titlestring | null
Job title.
hasEmailboolean | null
Whether an email is available to reveal.
hasPhoneboolean | null
Whether a direct phone is available to reveal.
emailstring | null
Work email. Null when the provider has none.
phonestring | null
Only returned when asked for with
phone=true. Null otherwise, or when not available.linkedinUrlstring | null
Null when the provider has none.
400A parameter is invalid
errorstring
Always
Invalid request.issuesobject[]
2 fields
pathstring
The invalid parameter, such as
limit.messagestring
401Missing, invalid or revoked API key
errorstring
What went wrong.
402Not enough credits for this call
errorstring
What went wrong.
404Shop or person not found, or the provider could not reveal this person
errorstring
What went wrong.
422hasPhone is false. Retry without phone=true
errorstring
What went wrong.
502The provider failed. Try again later
errorstring
What went wrong.
Monitors
Monitors and webhooks
A monitor watches a set of shops for the changes you pick (its topics) and sends them to your webhookUrl. Monitors belong to the user of the API key, and only that user can see or change them. ShopLens checks for changes about once a day.
Some topics are checked per market, because a shop can sell different products, at different prices and with different stock in each country. They are checked in each country of markets, or only in each shop's primary market when markets is empty. The other topics ignore it.
Each webhook is signed with the monitor's secret, so your endpoint can check it comes from ShopLens. You get the secret when you create the monitor and from Get a monitor. See Receiving webhooks for the headers, the signature and what each event sends.
Topics
| Topic | Fires when | Per market |
|---|---|---|
| product.created | A product is published | Yes |
| product.deleted | A product is unpublished or deleted | Yes |
| product.title_changed | A product's title changes | No |
| product.description_changed | A product's description changes | No |
| product.images_changed | A product's images are added, removed or reordered | No |
| product.price_changed | The price of any variant changes | Yes |
| product.compare_at_price_changed | The compare-at price of any variant changes | Yes |
| product.sale_started | A variant's compare-at price becomes higher than its price | Yes |
| product.sale_ended | A variant stops being on sale | Yes |
| product.out_of_stock | A variant sells out | Yes |
| product.back_in_stock | A sold-out variant is available again | Yes |
| variant.created | A product gets a new variant, such as a size or color | No |
| variant.deleted | A variant is removed from a product | No |
| collection.created | A collection is published | No |
Endpoints
Create a monitor
POST/api/v1/monitors1 credit
Subscribes to changes in up to 100 shops and sends them to your webhookUrl. The response includes the secret the webhooks are signed with.
- Every shop must be one ShopLens already has. An unknown domain answers
400with an issue atshops.<position>. Request it first with Get a shop and add it once it is ready. - Several monitors watching the same shop share one check of it, about once a day.
Body parameters
namestring | nulloptional
Up to 100 characters, to recognise the monitor.
webhookUrlstringrequired
HTTPS URL on a public domain that receives the webhooks, on the default port and without credentials. IP addresses, localhost and internal names such as .local or .internal are rejected. It is stored normalized, and a domain that resolves to a private address is not delivered to.
topicsstring[]required
Changes to watch, at least one. See Monitors and webhooks for the list.
marketsstring[]optional
Up to 50 countries, ISO 3166-1 alpha-2 such as
US, where the per-market topics are checked. Empty or omitted: each shop's primary market only.shopsstring[]required
1 to 100 shop domains, written as in Get a shop. Each must be a shop ShopLens already has.
activebooleanoptional
Defaults to
true.falsecreates it paused.
Responses
201The new monitor, with its secret
idstring
Monitor ID, a UUID.
namestring | null
webhookUrlstring
topicsstring[]
Changes watched.
marketsstring[]
Countries where per-market topics are checked. Empty: each shop's primary market only.
activeboolean
false: paused.
shopsobject[]
Shops watched, in the order they were added.
4 fields
idstring
gid://shopify/Shop/<number>namestring | null
domainstring | null
Primary domain, without
www.myshopifyDomainstring
createdAtstring
ISO 8601 date-time.
updatedAtstring
ISO 8601 date-time.
secretstring
Key the monitor's webhooks are signed with (Standard Webhooks). Keep it private.
400A field is invalid, or a shop is not one ShopLens has
errorstring
Always
Invalid request.issuesobject[]
2 fields
pathstring
The invalid parameter, such as
limit.messagestring
401Missing, invalid or revoked API key
errorstring
What went wrong.
402Not enough credits for this call
errorstring
What went wrong.
Endpoints
List your monitors
GET/api/v1/monitorsFree
Your monitors, paginated. Secrets are not included: get one monitor to see its secret.
Query parameters
limitintegeroptional
Number of results, 1 to 50. Defaults to 25.
cursorstringoptional
nextCursorfrom the previous response, as is.
Responses
200A page of monitors
monitorsobject[]
9 fields
idstring
Monitor ID, a UUID.
namestring | null
webhookUrlstring
topicsstring[]
Changes watched.
marketsstring[]
Countries where per-market topics are checked. Empty: each shop's primary market only.
activeboolean
false: paused.
shopsobject[]
Shops watched, in the order they were added.
4 fields
idstring
gid://shopify/Shop/<number>namestring | null
domainstring | null
Primary domain, without
www.myshopifyDomainstring
createdAtstring
ISO 8601 date-time.
updatedAtstring
ISO 8601 date-time.
hasNextPageboolean
nextCursorstring | null
Pass it as
cursorfor the next page. Null on the last page.
400A parameter is invalid
errorstring
Always
Invalid request.issuesobject[]
2 fields
pathstring
The invalid parameter, such as
limit.messagestring
401Missing, invalid or revoked API key
errorstring
What went wrong.
Endpoints
Get a monitor
GET/api/v1/monitors/{id}Free
One of your monitors, with the secret its webhooks are signed with.
Path parameters
idstringrequired
The monitor's
id.
Responses
200The monitor
idstring
Monitor ID, a UUID.
namestring | null
webhookUrlstring
topicsstring[]
Changes watched.
marketsstring[]
Countries where per-market topics are checked. Empty: each shop's primary market only.
activeboolean
false: paused.
shopsobject[]
Shops watched, in the order they were added.
4 fields
idstring
gid://shopify/Shop/<number>namestring | null
domainstring | null
Primary domain, without
www.myshopifyDomainstring
createdAtstring
ISO 8601 date-time.
updatedAtstring
ISO 8601 date-time.
secretstring
Key the monitor's webhooks are signed with (Standard Webhooks). Keep it private.
400A parameter is invalid
errorstring
Always
Invalid request.issuesobject[]
2 fields
pathstring
The invalid parameter, such as
limit.messagestring
401Missing, invalid or revoked API key
errorstring
What went wrong.
404No monitor with this id among yours
errorstring
What went wrong.
Endpoints
Update a monitor
PATCH/api/v1/monitors/{id}Free
Changes only the fields you send and keeps the rest. Send at least one.
topics,marketsandshopsreplace the whole list."active": falsepauses the monitor and"name": nullclears its name.
Path parameters
idstringrequired
The monitor's
id.
Body parameters
namestring | nulloptional
Up to 100 characters, to recognise the monitor.
nullclears it.webhookUrlstringoptional
HTTPS URL on a public domain that receives the webhooks, on the default port and without credentials. IP addresses, localhost and internal names such as .local or .internal are rejected. It is stored normalized, and a domain that resolves to a private address is not delivered to.
topicsstring[]optional
Changes to watch, at least one. See Monitors and webhooks for the list.
marketsstring[]optional
Up to 50 countries, ISO 3166-1 alpha-2 such as
US, where the per-market topics are checked. Empty or omitted: each shop's primary market only.shopsstring[]optional
1 to 100 shop domains, written as in Get a shop. Each must be a shop ShopLens already has.
activebooleanoptional
falsepauses the monitor without deleting it.
Responses
200The updated monitor
idstring
Monitor ID, a UUID.
namestring | null
webhookUrlstring
topicsstring[]
Changes watched.
marketsstring[]
Countries where per-market topics are checked. Empty: each shop's primary market only.
activeboolean
false: paused.
shopsobject[]
Shops watched, in the order they were added.
4 fields
idstring
gid://shopify/Shop/<number>namestring | null
domainstring | null
Primary domain, without
www.myshopifyDomainstring
createdAtstring
ISO 8601 date-time.
updatedAtstring
ISO 8601 date-time.
secretstring
Key the monitor's webhooks are signed with (Standard Webhooks). Keep it private.
400A field is invalid, or a shop is not one ShopLens has
errorstring
Always
Invalid request.issuesobject[]
2 fields
pathstring
The invalid parameter, such as
limit.messagestring
401Missing, invalid or revoked API key
errorstring
What went wrong.
404No monitor with this id among yours
errorstring
What went wrong.
Endpoints
Delete a monitor
DELETE/api/v1/monitors/{id}Free
Deletes the monitor and stops its webhooks. To stop it for a while, set active to false instead.
Path parameters
idstringrequired
The monitor's
id.
Responses
204Deleted
400A parameter is invalid
errorstring
Always
Invalid request.issuesobject[]
2 fields
pathstring
The invalid parameter, such as
limit.messagestring
401Missing, invalid or revoked API key
errorstring
What went wrong.
404No monitor with this id among yours
errorstring
What went wrong.
Webhooks
Receiving webhooks
Webhooks follow the Standard Webhooks specification. Each change is one POST to the monitor's webhookUrl with a JSON body, signed with the monitor's secret. The Standard Webhooks libraries check the signature in one call. Compute it over the raw body, before you parse it.
Headers
webhook-id
The event's id. It stays the same if the event is sent again, so use it to skip events you already handled.
webhook-timestamp
When it was sent, in Unix seconds. Reject events older than a few minutes.
webhook-signature
v1,and the base64 HMAC-SHA256 of{webhook-id}.{webhook-timestamp}.{body}, keyed with the base64-decoded part of the secret afterwhsec_.
Delivery
- A webhook counts as delivered when your endpoint answers any
2xxwithin 10 seconds. Redirects are not followed. - One event covers one product or one collection. When several variants of a product change in the same check, they arrive together in
data.variants. - One change can fire several topics. Putting a variant on sale fires
product.sale_started,product.price_changedandproduct.compare_at_price_changedwhen the monitor watches all three, each as its own webhook. Watch only the topics you need.
Body
idstring
Event ID, the same as the webhook-id header.
eventTypestring
One of the topics. See each event below.
createdAtstring
ISO 8601.
monitorobject
The monitor that sent it.
2 fields
idstring
namestring | null
shopobject
The shop that changed.
3 fields
idstring
gid://shopify/Shop/<id>
domainstring | null
namestring | null
marketstring | null
Country where the change was seen, for per-market topics; null for the others.
dataobject
Depends on eventType. See each event below.
Product
idstring
gid://shopify/Product/<id>
titlestring
handlestring
descriptionstring
Plain text, without HTML.
shopUrlstring | null
Public product page; null if the product is not on the online store.
featuredImagestring | null
URL of the featured image.
merchantProductTypestring | null
Product type as the merchant wrote it.
taxonomyCategorystring | null
Readable Shopify Product Taxonomy path, such as Apparel & Accessories > Clothing.
Variant
Priced in the event's market, or in the shop's primary market when market is null.
idstring
gid://shopify/ProductVariant/<id>
titlestring
Such as M / White.
skustring | null
barcodestring | null
Such as the UPC, EAN or ISBN.
pricestring
Decimal, such as 39.00.
compareAtPricestring | null
Decimal; null if the variant has none.
currencystring
ISO 4217 currency of both prices.
availableForSaleboolean
Whether it can be bought in the market.
Collection
idstring
gid://shopify/Collection/<id>
titlestring
handlestring
shopUrlstring | null
Public collection page; null if the collection is not on the online store.
Webhook event
product. created
A product is published in a market.
Checked in each of the monitor's markets.
data
productProduct
The new product.
8 fields
idstring
gid://shopify/Product/<id>
titlestring
handlestring
descriptionstring
Plain text, without HTML.
shopUrlstring | null
Public product page; null if the product is not on the online store.
featuredImagestring | null
URL of the featured image.
merchantProductTypestring | null
Product type as the merchant wrote it.
taxonomyCategorystring | null
Readable Shopify Product Taxonomy path, such as Apparel & Accessories > Clothing.
variantsVariant[]
Every variant of the product.
8 fields
idstring
gid://shopify/ProductVariant/<id>
titlestring
Such as M / White.
skustring | null
barcodestring | null
Such as the UPC, EAN or ISBN.
pricestring
Decimal, such as 39.00.
compareAtPricestring | null
Decimal; null if the variant has none.
currencystring
ISO 4217 currency of both prices.
availableForSaleboolean
Whether it can be bought in the market.
Webhook event
product. deleted
A product is unpublished from a market or deleted.
Checked in each of the monitor's markets.
data
productProduct
The product as it was last seen.
8 fields
idstring
gid://shopify/Product/<id>
titlestring
handlestring
descriptionstring
Plain text, without HTML.
shopUrlstring | null
Public product page; null if the product is not on the online store.
featuredImagestring | null
URL of the featured image.
merchantProductTypestring | null
Product type as the merchant wrote it.
taxonomyCategorystring | null
Readable Shopify Product Taxonomy path, such as Apparel & Accessories > Clothing.
Webhook event
product. title_changed
A product's title changes.
The same in every market, so market is null.
data
productProduct
The product, with its new title.
8 fields
idstring
gid://shopify/Product/<id>
titlestring
handlestring
descriptionstring
Plain text, without HTML.
shopUrlstring | null
Public product page; null if the product is not on the online store.
featuredImagestring | null
URL of the featured image.
merchantProductTypestring | null
Product type as the merchant wrote it.
taxonomyCategorystring | null
Readable Shopify Product Taxonomy path, such as Apparel & Accessories > Clothing.
previousobject
The title before the change.
1 field
titlestring
currentobject
The title now.
1 field
titlestring
Webhook event
product. description_changed
A product's description changes.
The same in every market, so market is null.
data
productProduct
The product, with its new description.
8 fields
idstring
gid://shopify/Product/<id>
titlestring
handlestring
descriptionstring
Plain text, without HTML.
shopUrlstring | null
Public product page; null if the product is not on the online store.
featuredImagestring | null
URL of the featured image.
merchantProductTypestring | null
Product type as the merchant wrote it.
taxonomyCategorystring | null
Readable Shopify Product Taxonomy path, such as Apparel & Accessories > Clothing.
previousobject
The description before the change, as plain text.
1 field
descriptionstring
currentobject
The description now, as plain text.
1 field
descriptionstring
Webhook event
product. images_changed
A product's images are added, removed or reordered.
The same in every market, so market is null.
data
productProduct
The product, with its new featured image.
8 fields
idstring
gid://shopify/Product/<id>
titlestring
handlestring
descriptionstring
Plain text, without HTML.
shopUrlstring | null
Public product page; null if the product is not on the online store.
featuredImagestring | null
URL of the featured image.
merchantProductTypestring | null
Product type as the merchant wrote it.
taxonomyCategorystring | null
Readable Shopify Product Taxonomy path, such as Apparel & Accessories > Clothing.
previousobject
The images before the change.
1 field
imagesstring[]
Image URLs, in order.
currentobject
The images now.
1 field
imagesstring[]
Image URLs, in order.
Webhook event
product. price_changed
The price of one or more of a product's variants changes in a market.
Checked in each of the monitor's markets.
data
productProduct
The product.
8 fields
idstring
gid://shopify/Product/<id>
titlestring
handlestring
descriptionstring
Plain text, without HTML.
shopUrlstring | null
Public product page; null if the product is not on the online store.
featuredImagestring | null
URL of the featured image.
merchantProductTypestring | null
Product type as the merchant wrote it.
taxonomyCategorystring | null
Readable Shopify Product Taxonomy path, such as Apparel & Accessories > Clothing.
variantsVariant[]
The variants whose price changed, as they are now. Each one adds previous.price, its price before the change.
9 fields
idstring
gid://shopify/ProductVariant/<id>
titlestring
Such as M / White.
skustring | null
barcodestring | null
Such as the UPC, EAN or ISBN.
pricestring
Decimal, such as 39.00.
compareAtPricestring | null
Decimal; null if the variant has none.
currencystring
ISO 4217 currency of both prices.
availableForSaleboolean
Whether it can be bought in the market.
previousobject
1 field
pricestring
Price before the change.
Webhook event
product. compare_at_price_changed
The compare-at price of one or more of a product's variants changes in a market.
Checked in each of the monitor's markets.
data
productProduct
The product.
8 fields
idstring
gid://shopify/Product/<id>
titlestring
handlestring
descriptionstring
Plain text, without HTML.
shopUrlstring | null
Public product page; null if the product is not on the online store.
featuredImagestring | null
URL of the featured image.
merchantProductTypestring | null
Product type as the merchant wrote it.
taxonomyCategorystring | null
Readable Shopify Product Taxonomy path, such as Apparel & Accessories > Clothing.
variantsVariant[]
The variants whose compare-at price changed, as they are now. Each one adds previous.compareAtPrice, its compare-at price before the change.
9 fields
idstring
gid://shopify/ProductVariant/<id>
titlestring
Such as M / White.
skustring | null
barcodestring | null
Such as the UPC, EAN or ISBN.
pricestring
Decimal, such as 39.00.
compareAtPricestring | null
Decimal; null if the variant has none.
currencystring
ISO 4217 currency of both prices.
availableForSaleboolean
Whether it can be bought in the market.
previousobject
1 field
compareAtPricestring | null
Compare-at price before the change.
Webhook event
product. sale_started
One or more of a product's variants go on sale in a market: their compare-at price becomes higher than their price.
Checked in each of the monitor's markets.
data
productProduct
The product.
8 fields
idstring
gid://shopify/Product/<id>
titlestring
handlestring
descriptionstring
Plain text, without HTML.
shopUrlstring | null
Public product page; null if the product is not on the online store.
featuredImagestring | null
URL of the featured image.
merchantProductTypestring | null
Product type as the merchant wrote it.
taxonomyCategorystring | null
Readable Shopify Product Taxonomy path, such as Apparel & Accessories > Clothing.
variantsVariant[]
The variants now on sale, as they are now. Each one adds previous, its prices before the sale, so you can tell a real price drop from a raised compare-at price.
9 fields
idstring
gid://shopify/ProductVariant/<id>
titlestring
Such as M / White.
skustring | null
barcodestring | null
Such as the UPC, EAN or ISBN.
pricestring
Decimal, such as 39.00.
compareAtPricestring | null
Decimal; null if the variant has none.
currencystring
ISO 4217 currency of both prices.
availableForSaleboolean
Whether it can be bought in the market.
previousobject
2 fields
pricestring
Price before the change.
compareAtPricestring | null
Compare-at price before the change.
Webhook event
product. sale_ended
One or more of a product's variants stop being on sale in a market.
Checked in each of the monitor's markets.
data
productProduct
The product.
8 fields
idstring
gid://shopify/Product/<id>
titlestring
handlestring
descriptionstring
Plain text, without HTML.
shopUrlstring | null
Public product page; null if the product is not on the online store.
featuredImagestring | null
URL of the featured image.
merchantProductTypestring | null
Product type as the merchant wrote it.
taxonomyCategorystring | null
Readable Shopify Product Taxonomy path, such as Apparel & Accessories > Clothing.
variantsVariant[]
The variants no longer on sale, as they are now. Each one adds previous, its prices during the sale.
9 fields
idstring
gid://shopify/ProductVariant/<id>
titlestring
Such as M / White.
skustring | null
barcodestring | null
Such as the UPC, EAN or ISBN.
pricestring
Decimal, such as 39.00.
compareAtPricestring | null
Decimal; null if the variant has none.
currencystring
ISO 4217 currency of both prices.
availableForSaleboolean
Whether it can be bought in the market.
previousobject
2 fields
pricestring
Price before the change.
compareAtPricestring | null
Compare-at price before the change.
Webhook event
product. out_of_stock
One or more of a product's variants sell out in a market.
Checked in each of the monitor's markets.
data
productProduct
The product.
8 fields
idstring
gid://shopify/Product/<id>
titlestring
handlestring
descriptionstring
Plain text, without HTML.
shopUrlstring | null
Public product page; null if the product is not on the online store.
featuredImagestring | null
URL of the featured image.
merchantProductTypestring | null
Product type as the merchant wrote it.
taxonomyCategorystring | null
Readable Shopify Product Taxonomy path, such as Apparel & Accessories > Clothing.
variantsVariant[]
The variants that sold out.
8 fields
idstring
gid://shopify/ProductVariant/<id>
titlestring
Such as M / White.
skustring | null
barcodestring | null
Such as the UPC, EAN or ISBN.
pricestring
Decimal, such as 39.00.
compareAtPricestring | null
Decimal; null if the variant has none.
currencystring
ISO 4217 currency of both prices.
availableForSaleboolean
Whether it can be bought in the market.
Webhook event
product. back_in_stock
One or more sold-out variants of a product can be bought again in a market.
Checked in each of the monitor's markets.
data
productProduct
The product.
8 fields
idstring
gid://shopify/Product/<id>
titlestring
handlestring
descriptionstring
Plain text, without HTML.
shopUrlstring | null
Public product page; null if the product is not on the online store.
featuredImagestring | null
URL of the featured image.
merchantProductTypestring | null
Product type as the merchant wrote it.
taxonomyCategorystring | null
Readable Shopify Product Taxonomy path, such as Apparel & Accessories > Clothing.
variantsVariant[]
The variants available again.
8 fields
idstring
gid://shopify/ProductVariant/<id>
titlestring
Such as M / White.
skustring | null
barcodestring | null
Such as the UPC, EAN or ISBN.
pricestring
Decimal, such as 39.00.
compareAtPricestring | null
Decimal; null if the variant has none.
currencystring
ISO 4217 currency of both prices.
availableForSaleboolean
Whether it can be bought in the market.
Webhook event
variant. created
A product gets one or more new variants, such as a new size or color.
The same in every market, so market is null.
data
productProduct
The product.
8 fields
idstring
gid://shopify/Product/<id>
titlestring
handlestring
descriptionstring
Plain text, without HTML.
shopUrlstring | null
Public product page; null if the product is not on the online store.
featuredImagestring | null
URL of the featured image.
merchantProductTypestring | null
Product type as the merchant wrote it.
taxonomyCategorystring | null
Readable Shopify Product Taxonomy path, such as Apparel & Accessories > Clothing.
variantsVariant[]
The new variants, priced in the shop's primary market.
8 fields
idstring
gid://shopify/ProductVariant/<id>
titlestring
Such as M / White.
skustring | null
barcodestring | null
Such as the UPC, EAN or ISBN.
pricestring
Decimal, such as 39.00.
compareAtPricestring | null
Decimal; null if the variant has none.
currencystring
ISO 4217 currency of both prices.
availableForSaleboolean
Whether it can be bought in the market.
Webhook event
variant. deleted
One or more variants are removed from a product.
The same in every market, so market is null.
data
productProduct
The product.
8 fields
idstring
gid://shopify/Product/<id>
titlestring
handlestring
descriptionstring
Plain text, without HTML.
shopUrlstring | null
Public product page; null if the product is not on the online store.
featuredImagestring | null
URL of the featured image.
merchantProductTypestring | null
Product type as the merchant wrote it.
taxonomyCategorystring | null
Readable Shopify Product Taxonomy path, such as Apparel & Accessories > Clothing.
variantsVariant[]
The removed variants, as they were last seen.
8 fields
idstring
gid://shopify/ProductVariant/<id>
titlestring
Such as M / White.
skustring | null
barcodestring | null
Such as the UPC, EAN or ISBN.
pricestring
Decimal, such as 39.00.
compareAtPricestring | null
Decimal; null if the variant has none.
currencystring
ISO 4217 currency of both prices.
availableForSaleboolean
Whether it can be bought in the market.
Webhook event
collection. created
A collection is published.
The same in every market, so market is null.
data
collectionCollection
The new collection.
4 fields
idstring
gid://shopify/Collection/<id>
titlestring
handlestring
shopUrlstring | null
Public collection page; null if the collection is not on the online store.
AI agents
MCP server
AI assistants get the same data from a remote MCP server (streamable HTTP, stateless). It exposes a few tools built around tasks, rather than one tool per endpoint. Tool calls spend credits from the same balance as the REST API, at the same price as the endpoint each tool calls, and follow the same rules: only successful calls are charged.
It signs you in with OAuth, following the MCP authorization spec. Add the server URL to your MCP client and it opens the ShopLens dashboard for you to sign in. No API key goes in the client's config.
A request without a valid access token answers 401 with a WWW-Authenticate header that points to /.well-known/oauth-protected-resource/mcp, which names the authorization server.
Tools
search_shops1 credit per shop returned
Search and rank shops, with the same filters as GET /shops.
get_shop1 credit
A shop's details, best sellers, product types and similar shops. For a shop not indexed yet, it asks to retry in a few minutes.
get_shop_ads40 credits
A shop's ads on Meta, Google and TikTok, the 20 most recent per platform.
list_shop_platform_ads40 credits per page
Every ad of a shop on one platform, paginated, with the same filters as GET /shops/{domain}/ads/{platform}.
get_shop_people5 credits
People at the shop's company, paginated.
reveal_shop_person25 credits, 150 with a phone
Reveal one person's email, LinkedIn and optionally phone. Only for explicit requests about one person.