Scan and price a product from a photo

You have a photo of a product and want its name and price back. This is the shortest path through POST /api/v1/scan: encode the image, call the endpoint, and handle a match or a miss.

POST /api/v1/scan takes a base64-encoded JPEG and returns a product identification. If your key is in account mode, that identification is matched against a real inventory — the caller’s own, or a connected merchant’s — and you get back an actual price and stock level, not just a label. The call costs 3¢ and is only billed on a successful response; a failed or rejected call never debits your wallet.

  1. 1

    Capture and base64-encode a JPEG

    Take or receive the product photo as a JPEG and base64-encode it before sending — the endpoint expects the raw base64 string with no data URI prefix (don’t send "data:image/jpeg;base64,...", just the encoded bytes).

    import { readFile } from 'node:fs/promises'
    
    const bytes = await readFile('./product-photo.jpg')
    const base64Jpeg = bytes.toString('base64') // no data URI prefix
  2. 2

    Call POST /api/v1/scan

    Send the base64 image in the request body with your x-api-key. Include an Idempotency-Key so that if the request times out and you retry, you get back the exact original response instead of running (and potentially paying for) the vision call twice.

    curl -X POST https://askbiz.co/api/v1/scan \
      -H "x-api-key: abz_live_your_key_here" \
      -H "Content-Type: application/json" \
      -H "Idempotency-Key: 5a9e2c1e-6b3f-4a2d-9c11-3f7e8a0b1c2d" \
      -d '{
        "image": "<base64-encoded JPEG>"
      }'
  3. 3

    Branch on found: true vs found: false

    A response with found: true means the photo matched an item in the resolved inventory — you get a real inventory_id, price, cost_price, stock_qty, and unit back. A response with found: false still identified the product (name is populated) but nothing in the catalog matched — price, inventory_id, stock_qty, and unit come back null. Handle both: show the real price on a match, or fall back to a manual-entry flow pre-filled with the identified name when there isn’t one.

    if (result.found) {
      // Real catalog match — show the merchant's own price and stock level
      console.log(`${result.name} — ${result.price} (${result.stock_qty} in stock)`)
    } else {
      // Vision model identified something, but it's not in the resolved inventory —
      // fall back to a manual entry flow, pre-filled with result.name
      console.log(`No catalog match for "${result.name}" — prompting for manual price entry`)
    }
  4. 4

    Optional — scope the scan to a connected merchant

    If you’re scanning on behalf of a merchant who isn’t your own AskBiz account, pass their user ID as merchant_id. This requires an active connection to that merchant that grants the read_inventory scope — set one up first with the connect-to-a-merchant guide. Without merchant_id, an account-mode key scans against its own inventory.

    const res = await fetch('https://askbiz.co/api/v1/scan', {
      method: 'POST',
      headers: {
        'x-api-key': process.env.ASKBIZ_API_KEY,
        'Content-Type': 'application/json',
        'Idempotency-Key': crypto.randomUUID(),
      },
      body: JSON.stringify({
        image: base64Jpeg,
        merchant_id: connectedMerchantUserId, // requires an active read_inventory connection
      }),
    })

What’s next

For the full parameter and error reference, see POST /api/v1/scan. To scope scans to a merchant who isn’t your own account, set up a connection first with Connect to a merchant. For the full idempotency contract and how retries interact with billing, see Errors and retries.

Scan and price products FAQ

What happens if the photo doesn’t match anything in the inventory?+

You get found: false with the vision model’s best identification in name, but inventory_id, price, stock_qty, and unit all come back null. You still get charged the 3¢ for the successful vision call — the miss is a valid, billed response, not an error. Use the identified name to pre-fill a manual entry flow.

Do I need to strip the "data:image/jpeg;base64," prefix before sending?+

Yes. The image field expects the raw base64 string only — no data URI prefix. If you’re capturing the photo from a browser <input type="file"> or canvas, strip everything before the comma in the resulting data URL before sending.

Why should I send an Idempotency-Key on every scan call?+

If a request times out on your end, you can’t tell whether the vision call actually ran server-side. Retrying with the same Idempotency-Key returns the original response instead of running the Groq vision pipeline again, so you never get double-charged or see two different results for one photo.

Can a generic-mode key match against a merchant’s inventory?+

No. Matching against a real inventory — your own or a connected merchant’s — requires an account-mode key. A generic-mode key still gets a raw product identification back, just with found: false and no catalog lookup, since there’s no account behind it to resolve inventory from.