Market NinjaMarket Ninja

API Overview

How to get a key, authenticate, and start using the Market Ninja API — the resolve-then-fetch model, limits, error format.

The Market Ninja API gives programmatic read access to the same crowdsourced dataset you see in the extension's own UI: products, prices (current and historical), sellers, and categories from Wildberries, Ozon, Yandex Market, and Lamoda.

The API is read-only and can't trigger a scrape on demand — it only reads data that's already in the dataset. New products enter the dataset exclusively through the Market Ninja browser extension: if a product isn't there yet, scrape it with the extension first, then it becomes available through the API.
Server-to-server only. A secret key is a long-lived credential and must never touch code that runs in a browser — the same reason Stripe secret keys never do.

Getting a key

Issued from the extension's options page — API Keys section, available on the Premium tier. The full key value (mn_live_...) is shown exactly once, at creation time — save it immediately, it can't be recovered later (only revoked and reissued).

Authentication

Every request needs this header:

Authorization: Bearer mn_live_your_key
curl -H "Authorization: Bearer mn_live_..." \
  "https://data.marketninja.ru/v1/products?page_size=3"

Core usage model: "resolve, then fetch"

No external integrator knows our internal product id up front — nothing tells you that in advance. Instead, you always start with whatever you already have:

Find the product via GET /products, passing whatever you have: a product URL (url), the marketplace's own SKU (external_id + marketplace), or your own internal SKU (seller_code).

Read the id off the data[].id field in the response — our internal identifier.

If you need to, fetch that product directly by id (GET /products/{id}) or its price history (GET /products/{id}/price-history) — though note the current price is already in the response from step 1; the dedicated call is only for trend analysis.

Detailed scenarios with examples: Finding products, Price history, Sellers, Categories, Brands, Bulk export, Scrape sessions (retrieving what you just scraped via the extension), and Webhooks (get notified when a session finishes instead of polling). Full docs on parameters and responses for every endpoint: Reference.

Error format

Every error comes back in one shape:

{ "error": { "code": "invalid_api_key", "message": "Invalid or revoked API key" } }
HTTP statuserror.codeMeaning
400validation_errorInvalid query parameters.
401invalid_api_keyThe key is missing, malformed, unknown, revoked, or expired.
403subscription_lapsedThe key is valid, but Premium isn't currently active.
404not_foundNo product with that id exists.
429rate_limitedPer-minute request rate exceeded — see Retry-After.
429quota_exceededDaily data-volume quota exhausted — resets at midnight UTC.
500internal_errorAn internal server error — safe to retry later.
403 and 401 are different problems for an integrator: "your key is wrong" (check the value) vs. "go resubscribe" (the key itself works, but access is paused). Don't handle them the same way in your code — the message shown to your user should differ.

Limits: two independent mechanisms

  • Request rate limit (rate_limited) — bounds how fast you can call, resets every minute. X-RateLimit-Limit/X-RateLimit-Remaining/Retry-After headers.
  • Daily data-volume quota (quota_exceeded) — bounds how many product records you can pull in a day (counts records actually returned, not requests — a single page_size=500 call costs more quota than page_size=10). Resets at midnight UTC. X-Quota-Limit/X-Quota-Used headers.

Both header sets only appear on the 429 response itself — not on successful responses.

Worth knowing

  • Cache the id you resolve. It never changes — if you're polling the same product regularly (e.g. a brand reconciling its catalog daily), save the id from the first search and call directly by id from then on instead of repeating GET /products — saves both a request and quota.
  • unverified/flagged data is never hidden by default. trust_status is always present in every product response — whether to trust a not-yet-corroborated record is your call, not something the API decides for you.
  • Pagination doesn't require knowing the page count up front — read meta.total from the response and keep going until you have what you need.

Last updated on

On this page