Public API · Products

Products API — every SKU, programmable.

CRUD over your catalogue, plus on-demand observation kicks. The same shapes the dashboard renders, behind a stable URL.

Base URL
api.priceroom.com/api/public/v1
Format
application/json
Auth
Bearer token
Rate limit
600 req/min/key

Catalogue CRUD

Cookie-session endpoints used by the in-app product workflows. The same shapes are exposed under /api/public/v1/products for external integrations.

GET/api/public/v1/productsBearer pk_live_…

List products with cursor pagination. Filter by brand or category (case-insensitive contains).

Body
Returns
{ data: Product[], nextCursor: string | null, count: number }
curl "https://api.priceroom.com/api/public/v1/products?limit=50&brand=Sony" \
  -H "Authorization: Bearer pk_live_XXX"
{
  "data": [
    {
      "id": "6630e1...",
      "sku": "WH-1000XM5",
      "title": "Sony WH-1000XM5 Wireless Headphones",
      "brand": "Sony",
      "category": "Audio",
      "tier": "premium",
      "ourPrice": 29900,
      "ourPriceUpdatedAt": "2026-04-28T10:22:03Z",
      "costPrice": 18000,
      "targetPrice": 30900,
      "currency": "EUR",
      "scrapeEnabled": true,
      "scrapeIntervalHours": 12,
      "lastScrapedAt": "2026-05-02T08:00:00Z",
      "createdAt": "2026-04-13T16:05:12Z",
      "updatedAt": "2026-05-02T09:11:48Z"
    }
  ],
  "nextCursor": "eyJ0IjoxNzE5NjIzMjAwfQ==",
  "count": 1
}
POST/api/productsCookie session

Create a product in the tenant catalogue.

Body
sku*, title*, description? (≤ 4,000 chars), url? (https URL, ≤ 2,048 chars), brand?, category?, subcategory?, ourPrice? (cents), costPrice? (cents), targetPrice? (cents), currency? (default EUR), vintage? (1900–2100), bottleSizeMl?, packagingSize?, region?, appellation?, b2bMinPrice?, attributes? (record, ≤ 50 keys)
Returns
Created Product document
curl -X POST https://app.priceroom.com/api/products \
  -H "Content-Type: application/json" \
  -H "Cookie: pr_session=..." \
  -d '{
    "sku": "WH-1000XM5",
    "title": "Sony WH-1000XM5",
    "brand": "Sony",
    "ourPrice": 29900,
    "currency": "EUR"
  }'
GET/api/products/[id]Cookie session

Fetch a single product by Mongo ObjectId.

Body
Returns
Full Product document
PATCH/api/products/[id]Cookie session

Partial update. Same fields as POST, all optional.

Body
sku?, title?, description?, url?, brand?, category?, subcategory?, gtin?, ourPrice?, costPrice?, targetPrice?, tier?, currency?, vintage?, attributes? …
Returns
Updated Product document
DELETE/api/products/[id]Cookie session

Soft-delete (sets deletedAt). The doc is not removed.

Body
Returns
{ ok: true }

Live observations

Trigger or query competitor-price observations attached to a product.

POST/api/products/[id]/scrape-nowCookie session

Kick off an ad-hoc scrape across every enabled CompetitorProduct matched to this product. Returns the new run id; poll /api/scrape-runs/[runId] for status.

Body
Returns
{ ok: true, runId: string, status: 'running', targetsCount: number } — the crawl runs in the background; poll /api/scrape-runs/[runId] for status, counts and per-competitor results
curl -X POST https://app.priceroom.com/api/products/6630e1.../scrape-now \
  -H "Cookie: pr_session=..."
GET/api/public/v1/observationsBearer pk_live_…

Flatten observation history across competitor matches. Filter by productId, competitorId, or ISO date range.

Body
Query: productId?, competitorId?, from? (ISO), to? (ISO), limit? (1–500, default 100)
Returns
{ data: Observation[], count: number, total: number }

Bulk XML import

Push a Business Central item export straight into your catalogue. Upsert by SKU; German numbers and dates are parsed for you, and the current price for a tier is the SALESPRICE with an empty VALIDUNTIL.

POST/api/public/v1/products/importBearer pk_live_…

Import products from Business Central XML — raw body (application/xml) or file uploads (one or more .xml, or a .zip). Requires a products:write key.

Body
Business Central XML — raw body (application/xml) or multipart/form-data file uploads (one or more .xml, or a .zip). Query: mode (upsert|insert_only), ourPriceTier (VK1B), b2bPriceTier (VK2), currency, recordPath. Needs a products:write key.
Returns
{ data: { jobId, status, stats: { inserted, updated, skipped, errors } }, count }
curl -X POST "https://app.priceroom.com/api/public/v1/products/import?mode=upsert" \
  -H "Authorization: Bearer pk_live_XXX" \
  -H "Content-Type: application/xml" \
  --data-binary @bc-items.xml
{
  "data": {
    "jobId": "6630e1...",
    "status": "succeeded",
    "stats": { "inserted": 142, "updated": 8, "skipped": 0, "errors": 0 }
  },
  "count": 150
}
GET/api/public/v1/products/import/[jobId]Bearer pk_live_…

Fetch the status, counts and per-record errors of a past import job.

Body
Returns
{ data: { jobId, source, status, stats, errors: [], startedAt, finishedAt } }

Errors

Every 4xx/5xx returns a stable JSON error. Use code for programmatic branching.

{
  "error": {
    "code": "invalid_api_key",
    "message": "The provided API key is invalid or revoked.",
    "requestId": "req_01HTX9..."
  }
}