Pear Onsite API
The Onsite API puts Pear's real-time where-to-buy engine inside your own website. Send a product identifier and a shopper location; get back the retailers that can fulfill it, live availability and pricing, and buy links — ready to render in any experience you design.
It is the same availability engine that powers Pear's shoppable landing pages and product locator, exposed as a clean, versioned JSON API so your team owns the front end completely. No iframes, no Pear UI — just data.
Live availability
Store-level in-store, pickup, and ship-to-home status recomputed continuously across thousands of retailers.
UPC-first
Query with the GTIN-12/UPC codes already in your product catalog. No retailer-specific IDs to manage.
Buy links & carts
Deep links to retailer product pages and multi-product add-to-cart handoffs, with built-in click attribution.
Any country code
Any ISO 3166-1 alpha-2 country — US, CA, MX, GB, AU, FR, DE, and more — with the deepest retailer coverage in the US and Canada.
Base URL & conventions
https://api.pearcommerce.com/onsite-api/v1
- All requests and responses are JSON. Field names use
snake_case. - The API is path-versioned. Breaking changes ship under a new version prefix, never in place.
- Errors return a consistent envelope with a machine-readable
codeand arequest_idfor support. - Authentication is via API keys — see Authentication.
Quickstart#
Make your first availability call in under five minutes.
-
Create an API key
In Pear Vision, open Self-Serve Admin → API Keys and choose Create key. Pick a secret key for server-to-server calls, or a publishable key for calls directly from your site's JavaScript. The full key is shown once — store it somewhere safe.
-
Make your first request
Fetch availability for a UPC near a zip code, using your secret key from a backend:
bashcurl "https://api.pearcommerce.com/onsite-api/v1/availability?upcs=012345678905&zip=55401" \ -H "x-api-key: sk_live_4eC39HqLyjWDarjtT1zdp7dc" -
Render the result
Each retailer row includes everything a buy panel needs — logos, fulfillment modes, price, and two link variants:
click_url, the best buy link for the shopper (add-to-cart where possible), andpdp_url, a direct link to the retailer product page.json — response (abbreviated){ "location": { "zip": "55401", "city": "Minneapolis", "state": "MN", "country": "US", "lat": 44.9778, "lng": -93.2650 }, "products": [ { "upc": "012345678905", "name": "Classic Sea Salt, 26 oz", "image_url": "https://assets.pearcommerce.com/upc-images/012345678905.png", "retailers": [ { "retailer": { "id": "walmart", "name": "Walmart", "logo_url": "https://assets.pearcommerce.com/retailer-logos/walmart.png" }, "price": { "value": 3.48, "currency": "USD" }, "availability": { "in_store": "available", "ship_to_home": "available" }, "links": { "click_url": "https://api.pearcommerce.com/onsite-api/v1/out/eyJ2IjoxLCJj...", "pdp_url": "https://www.walmart.com/ip/43893189" } } ] } ] }
Calling from the browser
Publishable keys may be used from your site's front end. Register each origin (for example
https://www.yourbrand.com) on the key in Vision; browsers calling from any other
origin receive 403 origin_not_allowed.
const res = await fetch(
"https://api.pearcommerce.com/onsite-api/v1/availability?upcs=012345678905&zip=55401",
{ headers: { "x-api-key": "pk_live_8kD2nQ4vXbM9sR1yTfH6wZaL" } }
);
if (!res.ok) throw new Error("Onsite API error: " + res.status);
const data = await res.json();
const rows = data.products[0].retailers; // render your buy panel
Authentication#
Every request authenticates with an API key tied to your brand. Keys are created, rotated, and revoked in Pear Vision → API Keys. Two key types serve two integration styles.
| Key type | Prefix | Used from | Protection |
|---|---|---|---|
| Secret | sk_live_… |
Your backend only | Full access within your brand scope. Never ship it in client code, mobile apps, or public repos. |
| Publishable | pk_live_… |
Browser JavaScript | Works only from origin domains you register on the key. Lower default rate limits. |
Send either type in the x-api-key header:
curl "https://api.pearcommerce.com/onsite-api/v1/retailers" \
-H "x-api-key: sk_live_4eC39HqLyjWDarjtT1zdp7dc"
Optional: HMAC request signing
For server-to-server integrations that need request integrity on top of key secrecy, sign each request with your secret key instead of sending it as a bearer. Four headers are required:
| Header | Description |
|---|---|
x-client-id | Your client ID, shown alongside your keys in Vision. |
x-timestamp | Unix seconds. Requests more than 5 minutes old are rejected. |
x-nonce | Unique random value per request (max 128 chars). Reused nonces are rejected as replays. |
x-signature | Lowercase hex HMAC-SHA256 of client_id + request_path + timestamp + nonce, keyed with your secret key. |
CLIENT_ID="cl_9f2a7c1e"
SECRET="sk_live_4eC39HqLyjWDarjtT1zdp7dc"
PATH_ONLY="/onsite-api/v1/availability"
TS=$(date +%s)
NONCE=$(openssl rand -hex 8)
SIG=$(printf '%s' "${CLIENT_ID}${PATH_ONLY}${TS}${NONCE}" | openssl dgst -sha256 -hmac "$SECRET" | awk '{print $2}')
curl "https://api.pearcommerce.com${PATH_ONLY}?upcs=012345678905&zip=55401" \
-H "x-client-id: $CLIENT_ID" -H "x-timestamp: $TS" \
-H "x-nonce: $NONCE" -H "x-signature: $SIG"
const crypto = require("crypto");
function signedHeaders(clientId, secret, pathOnly) {
const ts = Math.floor(Date.now() / 1000).toString();
const nonce = crypto.randomBytes(8).toString("hex");
const sig = crypto
.createHmac("sha256", secret)
.update(clientId + pathOnly + ts + nonce)
.digest("hex");
return { "x-client-id": clientId, "x-timestamp": ts, "x-nonce": nonce, "x-signature": sig };
}
Rotation & revocation
- Up to two active keys per type per brand, so you can rotate without downtime: create the new key, deploy it, then revoke the old one.
- Keys are shown in full exactly once. Pear stores only a SHA-256 hash — a leaked key can be revoked but never recovered from us.
- Revoking a publishable key immediately stops browser calls from its registered origins.
API Reference#
All endpoints are relative to https://api.pearcommerce.com/onsite-api/v1 and require
an API key as described in Authentication. Location-scoped
endpoints accept exactly one location mode: zip (+ optional country,
default US) or lat/lng.
Products#
/products/{upcs}
Catalog information for up to 50 comma-separated UPC / GTIN-12 codes.
curl "https://api.pearcommerce.com/onsite-api/v1/products/012345678905,012345678906" \
-H "x-api-key: sk_live_4eC39HqLyjWDarjtT1zdp7dc"
{
"products": [
{
"upc": "012345678905",
"name": "Classic Sea Salt, 26 oz",
"brand": "Morton",
"description": "Plain table salt with clean, classic taste.",
"image_url": "https://assets.pearcommerce.com/upc-images/012345678905.png",
"gtins": ["00012345678905", "012345678905"]
}
]
}
| Field | Type | Description |
|---|---|---|
upc | string | Canonical UPC for the product. |
name, brand, description | string | Catalog metadata. |
image_url | string | Product image, CDN-hosted. |
gtins | string[] | All GTIN aliases known for the product (12/13/14-digit). |
Availability#
/availability
The core where-to-buy call: for up to 25 UPCs and a shopper location, the retailers that can fulfill each product, with per-store pricing, fulfillment modes, and buy links.
| Parameter | Type | Description | |
|---|---|---|---|
upcs | string | required | Comma-separated UPC/GTIN-12s, max 25. |
zip | string | location | Shopper zip or postal code. Pair with country. |
lat, lng | number | location | Shopper coordinates. Use instead of zip, never both. |
country | string | optional | Any ISO 3166-1 alpha-2 code; default US. E.g. US, CA, MX, GB, AU, FR, DE. Coverage depends on the retailers configured for your brand in that country. |
retailer_ids | string | optional | Comma-separated retailer IDs to include (see Retailers). |
include_out_of_stock | boolean | optional | Include unavailable rows (default false). |
max_results | integer | optional | Cap retailer rows per product. |
session_id | string | optional | Your shopper-session identifier. When present, the call saves a page-load (impression) record in Vision keyed to this session, joining impressions to downstream clicks. |
page_url | string | optional | Page where results render; recorded with the page load for page-level attribution reporting. |
src | string | optional | Free-form source tag (e.g. pdp, recipe, store-locator). |
curl "https://api.pearcommerce.com/onsite-api/v1/availability?upcs=012345678905&zip=55401&session_id=shopper-8f31a&src=pdp" \
-H "x-api-key: sk_live_4eC39HqLyjWDarjtT1zdp7dc"
{
"location": { "zip": "55401", "city": "Minneapolis", "state": "MN", "country": "US", "lat": 44.9778, "lng": -93.2650 },
"products": [
{
"upc": "012345678905",
"name": "Classic Sea Salt, 26 oz",
"image_url": "https://assets.pearcommerce.com/upc-images/012345678905.png",
"retailers": [
{
"retailer": {
"id": "walmart",
"name": "Walmart",
"logo_url": "https://assets.pearcommerce.com/retailer-logos/walmart.png",
"square_logo_url": "https://assets.pearcommerce.com/retailer-logos/walmart-square.png"
},
"store": {
"id": "walmart-2412",
"name": "Walmart Supercenter - Minneapolis",
"address": "1200 Washington Ave S",
"city": "Minneapolis", "state": "MN", "postal_code": "55415",
"phone": "(612) 555-0148",
"lat": 44.9712, "lng": -93.2511,
"distance_miles": 1.6,
"hours": "6:00 AM - 11:00 PM",
"fulfillment": ["in_store", "pickup", "ship_to_home"]
},
"price": { "value": 3.48, "currency": "USD", "list_price": 3.98, "sale_price": 3.48 },
"availability": { "in_store": "available", "ship_to_home": "available" },
"links": {
"click_url": "https://api.pearcommerce.com/onsite-api/v1/out/eyJ2IjoxLCJjIjoibWFyeSIs...",
"pdp_url": "https://www.walmart.com/ip/43893189"
}
},
{
"retailer": { "id": "target", "name": "Target", "logo_url": "https://assets.pearcommerce.com/retailer-logos/target.png" },
"store": null,
"price": { "value": 3.49, "currency": "USD" },
"availability": { "in_store": "unknown", "ship_to_home": "available" },
"links": {
"click_url": "https://api.pearcommerce.com/onsite-api/v1/out/eyJ2IjoxLCJjIjoiY2xfOWYy...",
"pdp_url": "https://www.target.com/p/-/A-11553427"
},
"backup_upcs": [{ "upc": "012345678913", "availability": { "ship_to_home": "available" } }]
}
]
}
]
}
"unknown" for that row while a fresh check runs; subsequent calls within
minutes return current data.
| Field | Description |
|---|---|
store | The nearest fulfilling store for that retailer, or null for online-only rows. |
availability.* | available, unavailable, or unknown per fulfillment mode. |
links.click_url | The URL to use for this row's buy action — deep-links to add-to-cart where the retailer supports it, otherwise the product page. Always present. |
links.pdp_url | Direct link to the retailer product page. |
backup_upcs | Substitute products (pack sizes, variants) available at the same retailer when the requested UPC is not. Omitted when empty — the field only appears when backups exist. |
Cart availability#
/availability/cart
Multi-product availability grouped by store, with a ready-to-use add-to-cart handoff per store. Built for recipe pages, routine bundles, and "buy the whole list" experiences.
upcs accepts optional quantities as upc:qty, e.g.
012345678905:2,012345678906:1. Location parameters match
Availability.
curl "https://api.pearcommerce.com/onsite-api/v1/availability/cart?upcs=012345678905:2,012345678906:1&zip=55401" \
-H "x-api-key: sk_live_4eC39HqLyjWDarjtT1zdp7dc"
{
"location": { "zip": "55401", "city": "Minneapolis", "state": "MN", "country": "US" },
"stores": [
{
"retailer": { "id": "walmart", "name": "Walmart", "logo_url": "https://assets.pearcommerce.com/retailer-logos/walmart.png" },
"store": { "id": "walmart-2412", "name": "Walmart Supercenter - Minneapolis", "distance_miles": 1.6 },
"totals": { "product_count": 2, "requested_count": 2, "total_price": 10.45, "currency": "USD" },
"add_to_cart_url": "https://api.pearcommerce.com/onsite-api/v1/out/eyJ2IjoxLCJjYXJ0Ijp0cnVl...",
"products": [
{ "upc": "012345678905", "status": "available", "quantity": 2, "price": { "value": 3.48, "currency": "USD" } },
{ "upc": "012345678906", "status": "available", "quantity": 1, "price": { "value": 3.49, "currency": "USD" } }
]
},
{
"retailer": { "id": "instacart", "name": "Instacart", "logo_url": "https://assets.pearcommerce.com/retailer-logos/instacart.png" },
"store": null,
"totals": { "product_count": 2, "requested_count": 2, "total_price": 11.07, "currency": "USD" },
"add_to_cart_url": "https://api.pearcommerce.com/onsite-api/v1/out/eyJ2IjoxLCJjYXJ0Ijoi...",
"products": [
{ "upc": "012345678905", "status": "available", "quantity": 2, "price": { "value": 3.79, "currency": "USD" } },
{ "upc": "012345678906", "status": "available", "quantity": 1, "price": { "value": 3.49, "currency": "USD" } }
]
}
]
}
add_to_cart_url resolves to the retailer's native
multi-item cart (for example a Walmart or Instacart pre-filled cart). Shoppers land with
the items already in their basket.
Experiences#
/experiences/{offer_id}
Read-only access to the merchandising configuration your team manages in Pear — retailer ordering, pinned retailers, and link strategy — so your rendering matches what is configured without re-implementing it.
{
"offer_id": 83421,
"retailer_sort": "manual",
"pinned_retailer_ids": ["walmart", "target"],
"link_strategy": "dtc_except_target_and_kroger",
"retailers": [
{ "id": "walmart", "name": "Walmart", "sort": 1 },
{ "id": "target", "name": "Target", "sort": 2 },
{ "id": "instacart", "name": "Instacart", "sort": 3 }
]
}
Experiences are scoped to your brand: an API key can only read offers belonging to its
vendor. Requests for other offers return 404 not_found.
Geocode#
/geocode
Resolve a zip or postal code to coordinates and locality names, in any supported country.
| Parameter | Type | Description | |
|---|---|---|---|
zip | string | required | Zip or postal code (e.g. 55401, M5V 1J1, 06000, SW1A 1AA, 2000, 75001, 10115). |
country | string | optional | Any ISO 3166-1 alpha-2 code; default US. E.g. US, CA, MX, GB, AU, FR, DE. |
{
"zip": "55401",
"city": "Minneapolis",
"state": "MN",
"country": "US",
"lat": 44.9778,
"lng": -93.2650
}
Retailers#
/retailers
The retailer catalog configured for your brand — the set that can appear in availability responses, with display assets. Pass a location to get only the retailers that would actually be loaded to serve that location, exactly as a retailer-list call would.
| Parameter | Type | Description | |
|---|---|---|---|
zip | string | location | Zip or postal code. Pair with country. |
lat, lng | number | location | Coordinates. Use instead of zip, never both. |
country | string | optional | Any ISO 3166-1 alpha-2 code; default US. |
With no location, the full brand catalog is returned. With a location, the list is scoped to retailers that serve that area — the same candidate set a retailer-list call would load for it.
curl "https://api.pearcommerce.com/onsite-api/v1/retailers?zip=55401" \
-H "x-api-key: sk_live_4eC39HqLyjWDarjtT1zdp7dc"
{
"retailers": [
{
"id": "walmart",
"name": "Walmart",
"logo_url": "https://assets.pearcommerce.com/retailer-logos/walmart.png",
"square_logo_url": "https://assets.pearcommerce.com/retailer-logos/walmart-square.png",
"ecommerce_url": "https://www.walmart.com"
},
{
"id": "instacart",
"name": "Instacart",
"logo_url": "https://assets.pearcommerce.com/retailer-logos/instacart.png",
"ecommerce_url": "https://www.instacart.com"
}
]
}
Clicks & attribution#
The Onsite API closes the loop between the availability you render and the shopper traffic you send to retailers, using three optional parameters and one link convention.
session_id— your shopper-session identifier. Availability calls that carry it save a page-load (impression) record in Vision, and clicks made through the resultingclick_urls join back to that same session — giving you the full purchase-intent funnel (impressions → clicks → per-retailer), not just outbound traffic.page_url— the page where results render, stored with the page load. Enables page-level purchase-intent reporting (which PDPs and recipes drive the most retailer traffic).src— a free-form placement tag so you can compare modules (hero buy box vs. sticky footer vs. store locator).
Every row includes click_url — always present, pointing at the best destination
for that row — and pdp_url, a direct link to the retailer product page. Use
click_url for your primary call to action.
Errors & rate limits#
Errors use a consistent envelope:
{
"error": {
"code": "origin_not_allowed",
"message": "Publishable key pk_live_8kD2… is not authorized for origin https://example.com.",
"request_id": "req_01J8ZQ7K0T3M4V2N8W6X1Y2Z3A"
}
}
| Status | Code | Meaning |
|---|---|---|
400 | invalid_params | Missing or malformed parameters — e.g. both zip and lat/lng, or more than 25 UPCs. |
401 | invalid_key | Missing, revoked, or malformed API key; bad HMAC signature or stale timestamp. |
403 | origin_not_allowed | Publishable key used from an unregistered browser origin. |
403 | scope_restricted | The key's brand scope does not cover the requested resource. |
404 | not_found | Unknown UPC or offer. |
429 | rate_limited | Rate limit exceeded; honor Retry-After. |
500 | internal | Something failed on our side. The request_id identifies it to support. |
Rate limits
| Key type | Default limit | Scope |
|---|---|---|
Publishable (pk_live_…) | 60 requests / minute | Per registered origin |
Secret (sk_live_…) | 600 requests / minute | Per key |
Every response carries X-RateLimit-Limit and X-RateLimit-Remaining.
Limited requests return 429 with a Retry-After header (seconds).
Need more headroom? Contact your Pear account team — limits are per-client configurable.
Changelog#
| Version | Changes |
|---|---|
v1 (draft) |
Initial release: products, availability, cart availability, experiences, geocode, retailers, and buy links. |