P Pear Developers Onsite API v1
API Reference Changelog Get API keys

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.

REAL-TIME

Live availability

Store-level in-store, pickup, and ship-to-home status recomputed continuously across thousands of retailers.

GTIN NATIVE

UPC-first

Query with the GTIN-12/UPC codes already in your product catalog. No retailer-specific IDs to manage.

COMMERCE

Buy links & carts

Deep links to retailer product pages and multi-product add-to-cart handoffs, with built-in click attribution.

GLOBAL

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

Base URL
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 code and a request_id for support.
  • Authentication is via API keys — see Authentication.

Quickstart#

Make your first availability call in under five minutes.

  1. 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.

  2. Make your first request

    Fetch availability for a UPC near a zip code, using your secret key from a backend:

    bash
    curl "https://api.pearcommerce.com/onsite-api/v1/availability?upcs=012345678905&zip=55401" \
      -H "x-api-key: sk_live_4eC39HqLyjWDarjtT1zdp7dc"
  3. 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), and pdp_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.

javascript
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 typePrefixUsed fromProtection
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:

bash
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:

HeaderDescription
x-client-idYour client ID, shown alongside your keys in Vision.
x-timestampUnix seconds. Requests more than 5 minutes old are rejected.
x-nonceUnique random value per request (max 128 chars). Reused nonces are rejected as replays.
x-signatureLowercase hex HMAC-SHA256 of client_id + request_path + timestamp + nonce, keyed with your secret key.
bash — signing example
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"
javascript — node.js signing
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 };
}
Note The signature covers the request path only — query parameters are not signed. Sign the path exactly as it appears in the URL, without the query string.

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#

GET /products/{upcs}

Catalog information for up to 50 comma-separated UPC / GTIN-12 codes.

bash
curl "https://api.pearcommerce.com/onsite-api/v1/products/012345678905,012345678906" \
  -H "x-api-key: sk_live_4eC39HqLyjWDarjtT1zdp7dc"
json — response
{
  "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"]
    }
  ]
}
FieldTypeDescription
upcstringCanonical UPC for the product.
name, brand, descriptionstringCatalog metadata.
image_urlstringProduct image, CDN-hosted.
gtinsstring[]All GTIN aliases known for the product (12/13/14-digit).

Availability#

GET /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.

ParameterTypeDescription
upcsstringrequiredComma-separated UPC/GTIN-12s, max 25.
zipstringlocationShopper zip or postal code. Pair with country.
lat, lngnumberlocationShopper coordinates. Use instead of zip, never both.
countrystringoptionalAny 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_idsstringoptionalComma-separated retailer IDs to include (see Retailers).
include_out_of_stockbooleanoptionalInclude unavailable rows (default false).
max_resultsintegeroptionalCap retailer rows per product.
session_idstringoptionalYour 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_urlstringoptionalPage where results render; recorded with the page load for page-level attribution reporting.
srcstringoptionalFree-form source tag (e.g. pdp, recipe, store-locator).
bash
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"
json — response
{
  "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" } }]
        }
      ]
    }
  ]
}
Freshness on request Calling this endpoint submits the requested UPCs and location into Pear's availability recompute pipeline — the same queue that keeps the product locator fresh. If a product-location pair hasn't been scanned recently, the first response may report "unknown" for that row while a fresh check runs; subsequent calls within minutes return current data.
FieldDescription
storeThe nearest fulfilling store for that retailer, or null for online-only rows.
availability.*available, unavailable, or unknown per fulfillment mode.
links.click_urlThe 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_urlDirect link to the retailer product page.
backup_upcsSubstitute 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#

GET /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.

bash
curl "https://api.pearcommerce.com/onsite-api/v1/availability/cart?upcs=012345678905:2,012345678906:1&zip=55401" \
  -H "x-api-key: sk_live_4eC39HqLyjWDarjtT1zdp7dc"
json — response
{
  "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" } }
      ]
    }
  ]
}
Cart handoff 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#

GET /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.

json — response
{
  "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#

GET /geocode

Resolve a zip or postal code to coordinates and locality names, in any supported country.

ParameterTypeDescription
zipstringrequiredZip or postal code (e.g. 55401, M5V 1J1, 06000, SW1A 1AA, 2000, 75001, 10115).
countrystringoptionalAny ISO 3166-1 alpha-2 code; default US. E.g. US, CA, MX, GB, AU, FR, DE.
json — response
{
  "zip": "55401",
  "city": "Minneapolis",
  "state": "MN",
  "country": "US",
  "lat": 44.9778,
  "lng": -93.2650
}

Retailers#

GET /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.

ParameterTypeDescription
zipstringlocationZip or postal code. Pair with country.
lat, lngnumberlocationCoordinates. Use instead of zip, never both.
countrystringoptionalAny 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.

bash
curl "https://api.pearcommerce.com/onsite-api/v1/retailers?zip=55401" \
  -H "x-api-key: sk_live_4eC39HqLyjWDarjtT1zdp7dc"
json — response (abbreviated)
{
  "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 resulting click_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:

json — error envelope
{
  "error": {
    "code": "origin_not_allowed",
    "message": "Publishable key pk_live_8kD2… is not authorized for origin https://example.com.",
    "request_id": "req_01J8ZQ7K0T3M4V2N8W6X1Y2Z3A"
  }
}
StatusCodeMeaning
400invalid_paramsMissing or malformed parameters — e.g. both zip and lat/lng, or more than 25 UPCs.
401invalid_keyMissing, revoked, or malformed API key; bad HMAC signature or stale timestamp.
403origin_not_allowedPublishable key used from an unregistered browser origin.
403scope_restrictedThe key's brand scope does not cover the requested resource.
404not_foundUnknown UPC or offer.
429rate_limitedRate limit exceeded; honor Retry-After.
500internalSomething failed on our side. The request_id identifies it to support.

Rate limits

Key typeDefault limitScope
Publishable (pk_live_…)60 requests / minutePer registered origin
Secret (sk_live_…)600 requests / minutePer 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#

VersionChanges
v1 (draft) Initial release: products, availability, cart availability, experiences, geocode, retailers, and buy links.