ProductMapper
REST ENDPOINT GET /api/jobs/{jobId}

Job Status & Polling

When POST /api/map can't resolve an identifier within its 8-second synchronous window, it hands back a jobId instead of blocking further. Poll this endpoint with that ID to retrieve the result once the background worker finishes.

Request Specification

HTTP REQUEST
GET /api/jobs/641a5293-91de-41bb-a019-db228c58f04e
Authorization: Bearer pm_live_xxxxxxxxxxxxxxxxxxxxxxxx

# No request body. Same auth as POST /api/map - see the callout below
# for what happens if you poll a jobId that isn't yours.
Authenticated, and scoped to whoever created the job
Same auth as /api/map - a personal API key or a signed-in session - is required, or the request gets a 401. Beyond that, this endpoint also checks that you're the one who submitted the job: polling someone else's jobId gets back the same { "status": "processing" } shape as a job that's still running, rather than a distinguishable "forbidden" response - so a caller can't tell "not yours" apart from "not done yet." A completed job's result is deleted from the queue automatically after 10 minutes either way.

Still Processing

The default response while the job hasn't finished yet.

HTTP 200 RESPONSE
HTTP/1.1 200 OK
Content-Type: application/json

{
  "status": "processing",
  "message": "Job is still in progress"
}

Completed

data is the exact same payload shape POST /api/map returns synchronously on a direct 200 - see that page for the full field reference.

HTTP 200 RESPONSE
HTTP/1.1 200 OK
Content-Type: application/json

{
  "status": "completed",
  "data": {
    "identifierType": "UPC",
    "identifierValue": "079361039905",
    "marketplace": "amazon",
    "marketplaceId": "B01NAL48FU",
    "amazonMarketplaceLabel": "US",
    "timestamp": 1787503535096,
    "listingDetails": {
      "asin": "B01NAL48FU",
      "title": "Nike Air Zoom Pegasus 36 Men's Running Shoes",
      "brand": "Nike",
      "manufacturer": "Nike Inc.",
      "description": "Lightweight breathable mesh upper. Foam midsole for responsive cushioning. Rubber outsole for durable traction.",
      "imageUrl": "https://m.media-amazon.com/images/I/71xyz.jpg",
      "price": 119.95,
      "formattedPrice": "$119.95",
      "listPrice": 129.95,
      "offerCount": 6,
      "offerCountFba": 4,
      "offerCountMerchant": 2,
      "isBuyBoxWinner": true,
      "salesRank": 1420,
      "category": "Athletic Shoes",
      "categoryGroup": "Sporting Goods",
      "packageQuantity": 1,
      "link": "https://www.amazon.com/dp/B01NAL48FU",
      "isActive": true,
      "identifiers": { "upc": "079361039905", "asin": "B01NAL48FU" },
      "marketplaceId": "ATVPDKIKX0DER",
      "marketplaceLabel": "US"
    }
  }
}

Failed / Not Found

The identifier has no match in Amazon's catalog.

HTTP 200 RESPONSE
HTTP/1.1 200 OK
Content-Type: application/json

{
  "status": "failed",
  "error": "NOT_FOUND"
}

Polling Multiple Jobs at Once

Tracking several in-flight lookups? GET /api/jobs?ids= checks many job IDs in a single request instead of one poll per job - same auth, same response shape per job as the single-job endpoint above, keyed by job ID. Up to 100 IDs per request; more than that returns 400.

HTTP REQUEST
GET /api/jobs?ids=641a5293-91de-41bb-a019-db228c58f04e,7c2e1d90-33aa-4f11-9c05-1a2b3c4d5e6f
Authorization: Bearer pm_live_xxxxxxxxxxxxxxxxxxxxxxxx

# Comma-separated job IDs, up to 100 per request.
HTTP 200 RESPONSE
HTTP/1.1 200 OK
Content-Type: application/json

{
  "jobs": {
    "641a5293-91de-41bb-a019-db228c58f04e": { "status": "processing" },
    "7c2e1d90-33aa-4f11-9c05-1a2b3c4d5e6f": {
      "status": "completed",
      "data": { "...": "same shape as the single-job Completed response above" }
    }
  }
}

Sample Poll Loop

JAVASCRIPT
# Recommended: poll every ~1.5s, stop once status is "completed" or
# "failed". A job that never resolves is still safe to keep polling -
# /api/jobs/{id} just keeps returning "processing" until a background
# worker eventually finishes it or you decide to give up client-side.

async function pollJob(jobId, attempt = 0) {
  const res = await fetch(`/api/jobs/${jobId}`);
  const json = await res.json();

  if (json.status === 'completed') return json.data;
  if (json.status === 'failed') throw new Error(json.error);

  await new Promise((r) => setTimeout(r, 1500));
  return pollJob(jobId, attempt + 1);
}