📋 Live API Reference: Field names and parameters may change. If you encounter field errors, check the latest OpenAPI spec at https://zoodata.ai/api/v1/openapi-spec for current field definitions. Keyword exception: the observation endpoints currently support
granularity=weekonly. Do not reintroduceday,month,lately_day, orlookbackDaysfrom a stale generated schema.
200M+ Amazon products. 22 endpoints. One API key.
export ZOODATA_API_KEY='hms_live_xxx'https://api.zoodata.ai/openapi/v2 — all POST with JSON bodyAuthorization: Bearer YOUR_API_KEYhttps://api.zoodata.ai (Bearer ZOODATA_API_KEY). Setting ZOODATA_BASE_URL to an untrusted host (anything other than api.zoodata.ai / *.zoodata.ai / localhost) makes the CLI refuse the request and withhold the key — the Bearer token is never sent to an untrusted host.{skill_base_dir}/scripts/zoodata.py (Python 3, stdlib-only). This data-layer reference skill allows the complete literal subcommand surface exposed by the bundled client's current top-level help.~/.zoodata/config.json; the Local Review Toolkit uses a private temporary working dir (created with mktemp -d, removed when the fallback completes) during the review fallback.Before selecting or invoking a bundled CLI command, read and apply references/cli-contract.md; reapply it after every result. It is the local source of truth for invocation, command identity, execution-environment permission handling, composite reuse, exit-status handling, authoritative transport status, retries, terminal interface failures, and partial results.
For this API-reference skill, a terminal interface failure must produce one concise localized notice stating that the ZooData API lookup could not be completed, followed by the succeeded and failed endpoint identifiers. Do not continue into endpoint guidance, schema interpretation, or another API call. Do not expose control tokens or internal retry logs unless the user requests diagnostics.
Commerce product/market search using a broad query → resolve and lock categoryPath before interpreting category-sensitive product, market, competitor, brand, or price-band results. An explicitly labeled products/search category probe may run without a locked category only to resolve that category. Do not apply this rule to /openapi/v2/keywords/* Keyword Intelligence endpoints: their keyword / query inputs are Amazon search queries and do not require categoryPath.
Brand/price-band queries MUST include --category to avoid cross-category contamination
Revenue = sampleAvgMonthlyRevenue directly. NEVER calculate avgPrice × totalSales (overestimates 30-70%)
Sales = monthlySalesFloor (lower bound). Fallback: 300,000 / BSR^0.65, tag as 🔍
Use API fields directly: sampleOpportunityIndex, sampleTop10BrandSalesRate — never reinvent
reviews/analysis needs 50+ reviews. Fallback chain when sample is insufficient:
realtime/product → ratingBreakdown (star distribution only, no themes)realtime/reviews (raw text, up to 100) + local Map/Reduce via the
Local Review Toolkit below — see "Local Review Toolkit" sectionAggregation endpoints (price-band, brand) without categoryPath produce severely distorted data
Price-band and brand endpoints only accept keyword (not categoryPath) — cross-validate returned products
mode is CLI-local, NOT an API parameter → zoodata.py expands --mode client-side into the filter sets in PRODUCT_MODES ( {skill_base_dir}/scripts/zoodata.py, 13 presets) before the request; sending mode raw → 422
CLI filter flags ≠ API field names → --sales-min → monthlySalesMin; --ratings-max (review count) → ratingCountMax, not ratingMax (a different valid field — max star rating — that returns wrong results silently, no 422). Pass categoryPath as a JSON array ( ["Electronics"]), never a string. Unknown fields ( salesMin, ratingsMax, …) → 422
BEFORE calling any endpoint, verify credentials are configured. The reliable check is python {skill_base_dir}/scripts/zoodata.py check — credentials-only by default, no endpoint calls and no credit usage; exits non-zero if no key is found in env vars OR config files. A [ -z "$ZOODATA_API_KEY" ] test alone is NOT sufficient — a user may have only ~/.zoodata/config.json set.
When no key is found through any mechanism:
STOP. Do not run the workflow. Do not call zoodata.py (you'll just get the same credential error and burn tokens).
Do NOT fall back to a "partial analysis from training data" / "industry common-sense headlines" / "for reference only" preview. Your training data is stale, has no per-ASIN granularity, and presenting it as analysis — even disclaimed — misrepresents what this skill produces. The deliverable is data-backed; without data, there is no deliverable.
Tell the user, in their language, all three of:
" ZOODATA_API_KEY is not set — I need this to run the analysis."
Get a free key (1,000 credits, no credit card): https://zoodata.ai/en/api-keys
Configure via one of:
export ZOODATA_API_KEY='hms_live_xxx' (session only)mkdir -p ~/.zoodata && chmod 700 ~/.zoodata && (umask 077; echo '{"api_key":"hms_live_xxx"}' > ~/.zoodata/config.json) (persistent; keep the file private — 0600)Optionally state in one sentence what the workflow will produce once the key is configured (deliverable shape only — no numbers, no market color, no "common sense" preview).
When zoodata.py returns a structured error with _transport.status=401,
error.status=401, and error.message="API Key invalid or expired":
STOP further endpoint calls immediately. Do not retry — a rejected key won't be accepted on a second try; every subsequent call will return 401 too.
Keep the selected credential authoritative. Do not inspect, compare, export, or switch to a lower-priority legacy credential after rejection. A legacy credential may be selected only when neither new source is configured; trying another endpoint or asking to continue does not change this precedence.
Report to the user:
Do not fabricate or guess the data the failed calls would have returned. This includes "training-data fallback" / "industry common-sense" headlines disguised as preview — those are fabrications.
When zoodata.py returns a structured error with _transport.status=402,
error.status=402, and error.message="API quota exhausted or subscription expired":
STOP further endpoint calls immediately. Do not retry. Do not switch endpoints as a workaround — 402 is account-level (key/subscription), not endpoint-level.
Report to the user with all four of:
Do not fabricate or guess the missing data to "complete" the report. Mark partial findings explicitly as partial. No "training-data fallback" / "industry common-sense" filler — substituting public-knowledge prose for missing endpoint data is still fabrication.
For every parsed HTTP response from zoodata.py, treat _transport.status as the authoritative outer status; response-body and nested status-like fields do not override it. When the CLI returns HTTP 422 / VALIDATION_ERROR, read the preserved structured server error on stdout, including its message/details and _query.params. Do not retry the unchanged request. Correct the named fields first; the CLI exits non-zero while preserving the server error fields for the calling agent. Keyword endpoints that expose granularity currently accept week only; do not send day, month, lately_day, or lookbackDays.
#EndpointPurposeKey Output1categoriesBrowse/search category treecategoryPath, productCount2markets/searchMarket-level metricssampleAvgMonthlySales, sampleAvgPrice, topSalesRate, sampleNewSkuRate3products/searchProduct search (20+ filter fields)asin, price, monthlySalesFloor, rating, ratingCount, fbaFee4products/competitorsCompetitor discoverysame fields as products/search5realtime/productLive ASIN detailrating, features, bestsellersRank[], buyboxWinner.price, variants6reviews/analysisAI review insights (11 dims)sentimentDistribution, consumerInsights, topKeywords7realtime/reviewsLive raw review text (cursor paginated, max 100)reviews[], nextCursor — feeds Local Review Toolkit8products/price-band-overviewPrice band summaryhottestBand, bestOpportunityBand, sampleOpportunityIndex9products/price-band-detailFull 5-band distributionpriceBands[] with sales, brands, ratings per band10products/brand-overviewBrand concentrationsampleTop10BrandSalesRate (CR10), sampleBrandCount11products/brand-detailPer-brand breakdownbrands[] with sales, revenue, sampleProducts12products/historyTime series (single ASIN per call)timestamps[], price[], bsr[], monthlySalesFloor[], rating[], ratingCount[], sellerCount[], title/imageUrl/bestSeller/newRelease/aPlus/inventoryStatus changelogs13/openapi/v2/keywords/detailKeyword summary from the nearest available weekly snapshotdata.context + data.items[].snapshotData with estimateSearchCount, abaRank, market/SKU/ad fields14/openapi/v2/keywords/market-profileMultidimensional weekly keyword profiledemand scale, Top3 concentration, ad activity, organic-entry difficulty, saturation, brand structure, organic benchmark, coverage15/openapi/v2/keywords/trendWeekly keyword time seriesdata.context + data.items[].series[] with search count, ABA rank, Top3 shares, period bounds15b/openapi/v2/keywords/trend-profileServer-calculated trend profile over fixed weekly windowstrend shape, volatility, normalized slope, direction consistency, ABA-rank evidence16/openapi/v2/keywords/extendsKeyword expansion / long-tail discoverydata.context + data.rows[].{matchData,keywordSnapshot}; may return empty rows[]17/openapi/v2/keywords/search-resultsWeekly keyword SERP snapshotdata.context + data.identity + data.rows[] with placement, product, and impression fields18/openapi/v2/keywords/competitor-product-keywordsKeyword set where an ASIN appears as a competitordata.context + data.identity + data.rows[] with keyword, position, demand, and traffic share19/openapi/v2/keywords/product-traffic-termsTraffic-driving keywords for an ASINsame response shape as competitor-product-keywords20/openapi/v2/keywords/product-traffic-terms-overviewWeekly ASIN all-keyword traffic-change overviewcurrent vs previous-period placement-level impression points, ORG first-3-page keyword entries/exits21/openapi/v2/keywords/product-traffic-terms-timelineASIN + keyword weekly timelinedata.context + data.items[].series[] with nested ASIN, traffic, placement, keyword, and ad groups
topN, listingAge, newProductPeriod are strings ( "10" not 10).data as an array — use .data[0] for the first record. But some commands may return non-array payloads inside data, so inspect the actual response shape before indexing.ratingCount not reviewCount everywherebsr (int) in products vs bestsellersRank (array) in realtimebuyboxWinner.price — NOT top-level price in realtimerealtime/product does NOT return: monthlySalesFloor, fbaFee, sellerCountrealtime/product cold-start: first call for an uncached ASIN may return success: true with an EMPTY data ( asin: "") while the live fetch warms up — retry once after a few seconds before concluding "no data" (still billed 1 credit per call)reviewCountMin/Max filters currently broken (API-56)reviews/analysis may 500 for certain ASINs (API-58) — retry different ASINcategories uses categoryKeyword (not keyword) and parentCategoryPath (not parentCategoryName)reviews/analysis: mode required ("asin"/"category"), use asins (plural array) not asinrealtime/reviews: returns 10 reviews/page fixed (no pageSize param); 1 credit/page; cursor-paginated; hard cap = 100 reviews (10 pages); supports marketplace US/UK onlykeywords/detail accepts exactly one of keyword / keywords[] (max 20), resolves date to the nearest available weekly snapshot, and returns input-ordered data.items[]; an unmatched item has status=empty, not top-level data: nullkeywords/market-profile accepts one of keyword / keywords[] (max 20), requires date, supports weekly granularity only, and returns input-ordered data.items[] with status=ok|empty. emptyReason is descriptive no-result text, not an enum. A subject-specific calculation failure can return HTTP 500 for the whole batch.keywords/trend-profile accepts one of keyword / keywords[] (max 20), requires date and 1–4 unique windowPeriods selected from 4/8/12/26, and supports weekly granularity only.keywords/extends requires query (not keyword), uses the latest available weekly snapshot, supports queryType = phrase or fuzzy, and may legitimately return empty data.rows[]; legacy date is optional and ignoredgranularity currently support week only. day, month, lately_day, and lookbackDays are unsupported. Use returned period boundaries instead of inferring a rolling window.keyword or query, use the Amazon search query / keyword phrase being analyzeddate or dateTo, prefer T-1 or earlier and avoid the current date unless the user explicitly asks for today's lookupkeywords/search-results requires date + keyword; exploreTypes values are ORG, SP, SB, SBV, SPRkeywords/competitor-product-keywords and keywords/product-traffic-terms require date + asin; both currently return the same live item shape, including trafficSharekeywords/product-traffic-terms-overview requires date + asin; it returns the latest weekly overview of all keyword impression traffic changes under that ASIN at or before the date, compared with the previous periodkeywords/product-traffic-terms-timeline requires asin + exactly one of keyword / keywords[] + dateFrom + dateTo; the date range cannot exceed 61 days and the series request has no pagination or sort parameterskeywords/search-results is the default source for explaining what products currently appear on a keyword SERP because it already returns listing-level product fieldsproducts/search is a broader ZooData product-database query and must not be presented as Amazon live keyword SERP orderingThese ten endpoints fill the gap between raw catalog data and search-demand/search-visibility intelligence.
Keyword value boundary:
amazon-keyword-traffic-analysis skill. This API reference does not prescribe a blanket caveat or one seller view for every subject./openapi/v2/keywords/detail#keyword / keywords[] (1–20), required date, optional marketplace, granularity=week onlydate to the nearest available weekly snapshot at or before that datedate; avoid current-date lookup unless explicitly requesteddata.context + data.items[], preserving request orderidentity, status=ok|empty, snapshotData, emptyReason, nullable errorCode, nullable errorMessagesnapshotData fields include estimateSearchCount, abaRank, Top3 click/conversion shares,
marketCharacteristics, totalSkuCount, SKU/brand/title coverage, organic/ad counts, and Top48 benchmarksestimateSearchCountWeekly, totalSkuCnt, or top-level data:null/openapi/v2/keywords/market-profile (metric layer)#keyword or keywords[] (1–20), required date, optional marketplace, granularity=week onlydata.context + data.items[], preserving request orderrequestedDate, resolvedDate, dataWindow.currentPeriod, scoringSpec, marketplace/site/granularityidentity, status=ok|empty, marketProfile, emptyReasonmarketProfile dimensions: marketCharacteristics, demandScale, top3Concentration, adActivity, top20OrganicEntryDifficulty, supplySaturation, brandStructure, organicProductBenchmarkcontext.scoringSpec ( id, version, scoreType, scoreRange, referenceScope). Scored dimensions expose supported, calculationStatus, unsupportedReason, level, interpretation, and levelEvidence.score.{value,direction}. There is no aggregate coverage object.marketCharacteristics.volatility and marketCharacteristics.annualSeasonality are independent evidence objects. Do not collapse their classifications, let one override the other, or invent peak periods from an empty list.status=empty, marketProfile=null, and a descriptive emptyReason; resolved context and scoringSpec may be nullempty result; do not automatically fan out all subjects into single calls.keywords/detail for source snapshot fields, metric-layer keywords/market-profile for stable deterministic profile objects, and the Agent + skill layer for evidence composition, confidence, explanations, limitations, and actions/openapi/v2/keywords/trend#keyword / keywords[] (1–20), required dateFrom / dateTo, optional marketplace, granularity=week only; maximum 93-day rangedateTo; avoid current-date lookup unless explicitly requesteddata.context + data.items[].series[], preserving request orderidentity, status=ok|empty, series[], emptyReason, nullable errorCode, nullable errorMessageperiodStartDate, periodEndDate, estimateSearchCount, abaRank,
abaTop3ClickShareRate, abaTop3ConversionShareRate/openapi/v2/keywords/trend-profile (metric layer)#keyword / keywords[] (1–20), required date, required unique windowPeriods[] selected from 4/8/12/26, optional marketplace, granularity=week onlydata.context + data.items[].rows[]; every requested window returns one row with rowContext, status=ok|empty, emptyReason, and trendProfilesearchDemand and abaRank dimensions with trend, trendPattern, and {value,direction} entries under trendEvidencekeywords/trend for trend-shape and volatility judgments. Descend only for required weekly points or fields omitted from the profile.status=ok window; use returned credit metadata./openapi/v2/keywords/extends#query; optional marketplace, page, pageSize, queryType, sortBy, sortOrder; no date is requiredquery, not keyword; queryType supports phrase and fuzzydate may be sent but is ignoreddata.context + data.query + data.queryType + data.rows[]matchData.{query,keyword,site,relevanceScore} and keywordSnapshot, whose
dataWindow.currentPeriod and snapshot metrics use the same current field families as keywords/detailterm, seedKeyword, or estimateSearchCountWeekly; empty rows[] is normal/openapi/v2/keywords/search-results#keyword / date, granularity=week only; optional marketplace, page, pageSize, exploreTypes, sortBy, sortOrderlookbackDays; day, month, and lately_day are unsupporteddate; avoid current-date lookup unless explicitly requesteddata.context + data.identity + data.rows[]latestObservedAt, exploreType, absolutePosition, pageIndex,
pagePosition, asin, title, brand, price, currency, link, imageLink, rating,
ratingCount, recentSales, hasVideo, estimateImpressionPoint,
keywordTotalEstimateImpressionPointproducts/search when the question is about observed keyword SERP composition or ordering/openapi/v2/keywords/competitor-product-keywords#asin / date, granularity=week only; optional marketplace, page, pageSize, exploreTypes,
keywordContains, sortBy, sortOrderlookbackDays; day, month, and lately_day are unsupported; use returned weekly period boundariesdate; avoid current-date lookup unless explicitly requesteddata.context + data.identity + data.rows[]latestObservedAt, exploreType, absolutePosition, pageIndex,
pagePosition, asin, keyword, estimateImpressionPoint, asinTotalEstimateImpressionPoint,
avgPosition, daysCoverageRate, observationCount, keywordEstimateSearchCount,
keywordEstimateSearchChangeCount, keywordEstimateSearchCountChangeRate, keywordAbaRank,
keywordAbaRankChangeCount, trafficShare/openapi/v2/keywords/product-traffic-terms#keywords/competitor-product-keywordsdate + granularity=week; use returned period boundariesdate; avoid current-date lookup unless explicitly requesteddata.context + data.identity + data.rows[]latestObservedAt, exploreType, absolutePosition, pageIndex,
pagePosition, asin, keyword, estimateImpressionPoint, asinTotalEstimateImpressionPoint,
avgPosition, daysCoverageRate, observationCount, keywordEstimateSearchCount,
keywordEstimateSearchChangeCount, keywordEstimateSearchCountChangeRate, keywordAbaRank,
keywordAbaRankChangeCount, trafficSharekeywords/competitor-product-keywords
field-for-field; keep the semantic distinction in output wording rather than assuming a unique schema/openapi/v2/keywords/product-traffic-terms-overview#asin, date, optional marketplacedate; avoid current-date lookup unless explicitly requesteddata is an object or nullperiodStartDate, periodEndDate, asin, site,
organicImpressionPoint, sponsoredProductImpressionPoint, sponsoredBrandImpressionPoint,
sponsoredBrandVideoImpressionPoint, sponsoredRecommendImpressionPoint,
organicImpressionPointPrev, sponsoredProductImpressionPointPrev,
sponsoredBrandImpressionPointPrev, sponsoredBrandVideoImpressionPointPrev,
sponsoredRecommendImpressionPointPrev, first3PagesNewOrganicKeywords,
first3PagesLostOrganicKeywords*Prev fields are previous-period baselines for the matching current impression-point fieldsperiodStartDate / periodEndDate; it does not return separate previous-period boundaries. A *Prev field may be null or absent when no previous-period value is available.first3PagesNewOrganicKeywords and first3PagesLostOrganicKeywords are arrays of objects with
keyword, pageIndex, and pagePositionfirst3PagesNewOrganicKeywords lists keywords newly entering ORG first three pages; first3PagesLostOrganicKeywords
lists keywords that dropped out of ORG first three pagesopenapi_v2_product_traffic_terms_overview,
asin="B01CGLCGRA", date="2026-06-29", marketplace="US"/openapi/v2/keywords/product-traffic-terms-timeline#asin, exactly one of keyword / keywords[] (1–20), dateFrom, dateTo, granularity=week only; optional marketplacelookbackDays, page, pageSize, sortBy, or sortOrder; day, month, and lately_day are unsupporteddateTo; avoid current-date lookup unless explicitly requesteddata.context + data.items[].series[], preserving keyword request orderidentity, status=ok|empty, series[], emptyReason, nullable errorCode, nullable errorMessageasinSnapshot, traffic, placement, keywordMetrics, and adActivity; keep their returned period boundaries separateasinSnapshot.latestPrice), BSR ( asinSnapshot.latestBsr,
asinSnapshot.latestSubBsr), sales ( asinSnapshot.latestMonthlySaleCount), rating
( asinSnapshot.latestRating, asinSnapshot.latestRatingCount), traffic estimate ( traffic.*
plus placement averages), and listing events ( asinSnapshot.latestTitle, asinSnapshot.latestMainImageLink)asinSnapshot; ORG/SP/SB/SBV/SPR impression points
in traffic; positions/pages/observation times in placement; weekly search/ABA fields and
metricWindow in keywordMetrics; observation/campaign/ad counts in adActivityWhen /reviews/analysis lacks aggregation (ASIN has <50 reviews or no daily snapshot),
fall back to live raw reviews + your own LLM. The toolkit does NOT call any external
LLM — you (the calling skill's LLM) perform the Map/Reduce steps.
Workflow:
bash
# 1. Fetch raw reviews (up to 100, cursor-paginated, ~60s, 10 credits at full)
zoodata.py reviews-raw --asin B0XXXXXXXX [--marketplace US] [--max-pages 10]
# 2. For EACH review, render the per-review Map prompt
zoodata.py review-tag-prompt --review '<single review JSON>' \
[--product-title "..."] [--product-category "..."]
# → Your LLM produces a JSON object with sentiment + 11 dimension arrays
# (mentioned_scenarios, mentioned_issues, mentioned_positives, mentioned_improvements,
# mentioned_buying_factors, mentioned_pain_points, user_profiles, mentioned_usage_times,
# mentioned_usage_locations, mentioned_behaviors, keywords)
# Suggested map parallelism: ~20 concurrent if your LLM supports it
# 3. Collect candidate phrases per dimension. For EACH dimension render the Reduce prompt
zoodata.py review-reduce-prompt --label-type positives \
--candidates '["comfortable","comfy","very comfortable",...]'
# → Your LLM produces {clusters: [{canonical, members}, ...]}
# Suggested chunk size for `keywords` dim when >150 candidates: 150 per call
# 4. Aggregate into reviews/analysis-compatible consumerInsights
zoodata.py review-aggregate --reviews raw.json --tagged tags.json --clusters clusters.json
# → Output shape matches /reviews/analysis: reviewCount, avgRating,
# sentimentDistribution, consumerInsights[], topKeywords[]
When to use the toolkit instead of reviews/analysis:
reviews/analysis returns sparse consumerInsights (missing dimensions)Datamarketsproducts/competitorsrealtime/productreviews/analysisrealtime/reviewsprice-bandbrandhistorySalessampleAvgMonthlySalesmonthlySalesFloor❌❌❌sampleSalesRatesampleGroupMonthlySalesmonthlySalesFloor[]PricesampleAvgPricepricebuyboxWinner.price❌❌bandMin/MaxPricesampleAvgPriceprice[]BSRsampleAvgBsrbsr (int)bestsellersRank[]❌❌❌❌bsr[]RatingsampleAvgRatingratingratingavgRatingrating (per review)sampleAvgRatingsampleAvgRatingrating[]ReviewssampleAvgReviewCountratingCountratingCountreviewCountreviews[] (raw text, max 100)❌sampleAvgRatingCountratingCount[]Insights❌❌❌✅ consumerInsights❌ (raw only — feeds Local Review Toolkit)❌❌❌ConcentrationtopSalesRate❌❌❌❌sampleTop3BrandSalesRateCR10❌Opportunity❌❌❌❌❌sampleOpportunityIndex❌❌
Strategy recommendations and subjective conclusions are NEVER 📊. Extreme growth (>200%) = 💡 only.
monthlySalesFloor) = lower-bound estimatemeta.creditsConsumed