Developers
Product data API
Publish your product catalog as JSON at a stable URL and read it from whatever you already run. No SDK, no polling of a dashboard, no page parsing on your side: one GET returns normalised products with the supplier’s price, your final price, stock levels and timestamps.
Overview
A feed is a saved slice of your ScrapeOra product library, published as JSON. You choose what goes in it (which suppliers, in stock or not, draft or live), who may read it (open, or behind a key), and how often the supplier links it is built from are re-imported.
It stays current on its own. When ScrapeOra re-reads a supplier the feed reflects the new price and stock on the next request, and when you change a pricing rule the new sell price is live immediately, because prices are worked out per request rather than stored.
Feeds are included on every plan, the free one as well. Create one under Automation → Developers → Product data API.
Addresses
Every account has its own key segment, so two accounts can never collide on a feed name.
GET /api/feeds/{account} your public feeds
GET /api/feeds/{account}/{feed} one feed's shape and size
GET /api/feeds/{account}/{feed}/products the products
GET /api/feeds/{account}/{feed}/products/{ref} one product, by id or SKUBase: https://scrapeora.com/api. Your exact addresses are shown on the feed’s page in ScrapeOra, ready to paste.
Authentication
A public feed needs none — it is an ordinary GET, readable from a browser, a shell or a serverless function:
curl "https://scrapeora.com/api/feeds/ab12cd34ef567890/storefront/products?limit=50"
A private feed takes one of its own keys. Keys are created in ScrapeOra, shown once, and read that one feed and nothing else — they cannot change anything in your account:
curl -H "Authorization: Bearer dfs_xxxxxxxxxxxxxxxxxxxx" \ "https://scrapeora.com/api/feeds/ab12cd34ef567890/storefront/products"
HTTP Basic works too, with the key id as the user and the secret as the password, for tools that only offer that. There is no ?key= parameter on purpose: query strings end up in access logs and browser history. A private feed answers 404 when no key is sent at all and 401 when a key is sent and is wrong, so a wrong address is distinguishable from a wrong key.
The products endpoint
limit— 1 to 250, default 50.offset— where the page starts, default 0.sort—updated(default),createdortitle. Prefix with-to reverse.updated_since— ISO 8601. Returns only what changed at or after that moment, which is how you sync without re-reading everything.
pagination.has_more tells you whether to ask for another page. matched is how many products fit the feed’s filters and total how many it will serve; when your plan caps the feed those two differ and plan_limited is true.
A product record
{
"feed": {
"name": "Storefront catalog",
"slug": "storefront-catalog",
"visibility": "public",
"last_refresh_at": "2026-10-28T06:00:11+00:00",
"generated_at": "2026-10-28T09:41:02+00:00"
},
"pagination": {
"total": 412, "matched": 412, "limit": 50, "offset": 0,
"returned": 50, "has_more": true, "plan_limited": false,
"max_products": 2500
},
"products": [
{
"id": "1f0c5a3e-...",
"title": "Ceramic Pour-Over Dripper",
"status": "active",
"vendor": "Hario",
"product_type": "Coffee",
"tags": ["kitchen", "coffee"],
"currency": "USD",
"in_stock": true,
"total_stock": 86,
"variant_count": 2,
"supplier": {
"id": "8ad1...", "name": "Acme Supply",
"kind": "website", "domain": "acme-supply.example",
"website": "https://acme-supply.example"
},
"supplier_url": "https://acme-supply.example/products/dripper",
"pricing": {
"currency": "USD",
"supplier_price": "10.00",
"supplier_price_max": "12.00",
"price": "20.99",
"price_max": "24.99",
"markup_percent": "109.90",
"rule": "global"
},
"first_seen_at": "2026-09-14T11:02:00+00:00",
"created_at": "2026-09-14T11:02:00+00:00",
"updated_at": "2026-10-28T06:00:44+00:00",
"last_checked_at": "2026-10-28T06:00:44+00:00",
"cover_image_url": "https://cdn.example/dripper.jpg",
"images": [{ "url": "...", "alt": null, "position": 0 }],
"options": [{ "name": "Size", "values": ["01", "02"] }],
"variants": [
{
"id": "6b2f...", "title": "01", "sku": "DRIP-01",
"option_values": ["01"],
"supplier_price": "10.00", "shipping_cost": "2.40",
"price": "20.99", "compare_at_price": null,
"markup_percent": "109.90", "price_source": "rule",
"stock": 42, "available": true,
"weight_grams": 310,
"image_url": "https://cdn.example/dripper-01.jpg"
}
]
}
]
}variants, images and description_html are each switchable per feed, so a price-and-stock sync can ask for a much smaller document than a full catalog import.
Prices and markup
supplier_price— what the supplier charges, as last read from their site or API.price— your sell price, worked out by your ScrapeOra pricing rules (markup, price endings, compare-at, optional shipping and duty).markup_percent— the increase between the two, as a percentage of the supplier’s price.
Amounts are decimal strings, never JSON numbers: JSON has one numeric type and it is binary floating point, which does not represent 19.99 exactly. Parse them with your language’s decimal type.
Currency is per product and amounts are never converted. Nothing is cached: change a markup in ScrapeOra and the very next request returns the new price.
The three dates
first_seen_at— when this product first appeared in this feed. A product that drops out of the filters and comes back keeps its original date.updated_at— when the product record last changed. Pair it withupdated_sincefor incremental syncs.last_checked_at— when ScrapeOra last read the supplier for this product, whether anything changed or not.
Caching and polling
Every response carries an ETag and a Cache-Control header with the max-age you set on the feed. Send the tag back and an unchanged feed answers 304 Not Modified with no body:
curl -H 'If-None-Match: W/"a1b2c3d4"' \ "https://scrapeora.com/api/feeds/ab12cd34ef567890/storefront/products"
Responses carry Access-Control-Allow-Origin: *, so a browser app can read a public feed directly. A private feed’s Cache-Control is marked private, so no shared cache keeps it.
Errors
404— no such account, feed or product; the feed is paused; or the feed is private and no key was sent.401— a key was sent and it is wrong, revoked or expired.429— too many requests from one address. Slow down, or use the ETag.
What each plan allows
Reading your products is included everywhere, including the free plan — what the plan decides is how many feeds you can publish, how many products one response carries, and whether a feed can be put behind a key.
- Free — a public feed, up to 20 products per response.
- Starter — public feeds, up to 500 products per response.
- Growth — public and key-protected feeds, up to 2,500 products per response.
- Agency — everything above (up to 10,000 products per response), plus full-access API tokens that can read and write over the whole REST API.
The exact numbers are on the pricing page, and your own account’s allowances are shown in ScrapeOra under Developers.
The full REST API
Feeds are read-only by design. To start imports, edit products or publish to a store from your own code, use the main REST API under /api/v1 with a personal API token:
curl -H "Authorization: Bearer scr_xxxxxxxxxxxxxxxxxxxx" \ "https://scrapeora.com/api/v1/products?limit=50"
Tokens come in two kinds: view-only, which may make GET requests, and full access, which acts as your account. Both are created in ScrapeOra under Developers → API & webhooks, and neither can create or revoke another token.
Try it in a minute
Create a free account, paste a few supplier links, and you have a working JSON endpoint. No card, no sales call.
Get started, it's free