Skip to main content

Changelog

All notable changes to the Retail Digitals Image API. We follow Semantic VersioningMAJOR.MINOR.PATCH:

  • MAJOR — Breaking changes. Announced with 90 days notice minimum. Old major version remains available for 12 months after the new major ships.
  • MINOR — Backwards-compatible additions (new fields, new endpoints, new optional params). No advance notice, listed here on release day.
  • PATCH — Bug fixes, documentation improvements, non-behavioral changes.

Pricing changes follow their own policy — see Pricing → Change policy.

Deprecation policy: fields and endpoints marked deprecated are removed no sooner than 90 days after the deprecation announcement, and only in a MAJOR bump.


[Unreleased]

Coming soon

  • Go and Ruby reference clients
  • Published npm / Packagist / PyPI packages
  • WebHook support for product updates
  • Optional per-request HMAC signing (in addition to Bearer)

[1.8.2] — 2026-08-10 (patch)

Fixed

  • GET /products/{barcode} now bills only for scopes and image variants that actually return data. Previously a requested scope (info, hechshers, nutrition, ingredients, catalog_meta, image_meta) or image variant was charged whenever it was requested, even if the barcode had nothing for it — a customer could pay full price for an empty response. Billing is now computed per-scope after the lookup: a scope with no data for that barcode costs nothing, regardless of whether you checked GET /metadata/{barcode} first. The product_meta flat bundle rate is retired in favor of always summing only the scopes that actually returned something — strictly the same or lower cost than before, never higher. POST /products/bulk-check and the metadata-availability endpoints are unaffected.

[1.8.1] — 2026-08-10

Fixed

  • POST /products/bulk-check now accepts 4-digit PLU codes. The barcode pattern was ^[0-9]{6,14}$, so produce PLUs were rejected. Now ^[0-9]{4,14}$, matching every other endpoint. Strictly wider — nothing that validated before can start failing.
  • Trailing slashes on /api/v1/* no longer redirect. POST /auth/token/ used to return a 301, and many HTTP clients silently downgrade a redirected POST to a GET or drop the body, so the call failed with no useful error. API paths are now exempt from the trailing-slash redirect; both forms return 200.
  • Malformed requests always return JSON. A request that omitted Accept: application/json previously got a 302 redirect to an HTML page on a validation failure, instead of a JSON 422 — an HTML body where JSON was expected, with the real error invisible. All /api/v1/* responses are now forced to JSON regardless of request headers.

[1.8.0] — 2026-08-03

Added — metadata availability

Catalogue coverage is partial (roughly 20–37% of barcodes per scope), so requesting a scope blind often meant paying for an empty payload. Four new endpoints report what a barcode actually has, by scope and by individual field, without returning any metadata values:

  • GET /metadata/{barcode} — field-level availability for one barcode, plus worth_requesting: the exact include= scopes that would return data for it.
  • POST /metadata/bulk-check — the same for up to 500 barcodes per call. Send fields: true for per-field detail. Supports Idempotency-Key.
  • GET /metadata/products — list barcodes filtered by metadata they have (has=) or lack (missing=), by scope (nutrition) or by individual field (nutrition.calories). Multiple keys are ANDed.
  • GET /metadata-fields — catalogue of every valid scope and field, with optional catalogue-wide coverage counts (counts=1).

New error code 422 invalid_metadata_key for an unknown scope or field in has=/missing=. New pricing keys, all readable from GET /pricing: metadata.get.per_barcode, metadata.bulk_check.per_barcode, metadata.bulk_check.fields.per_barcode, metadata.products.list, metadata.fields.catalogue. See Metadata availability for the full reference.

Unchanged

GET /products, GET /products/{barcode} and POST /products/bulk-check are not modified — same parameters, same response shapes, same costs. This release is purely additive; no existing integration needs to change.

Fixed

  • PLU codes are no longer excluded by min_digits. Every PLU is exactly 4 digits, so min_digits=5 and above silently dropped the entire produce range from GET /products. PLUs are now exempt from that filter. max_digits is unchanged.

[1.7.3] — 2026-07-20 (docs)

Catalog

  • Catalog passes 30,000 product images. Two July batches imported (studio raw shoots plus a curated pre-processed set). Where a new image duplicated an existing product under a differently-padded barcode, the image was merged into the canonical product and the newest photo became the active front; barcode aliases were extended so 11-digit UPC-A lookups resolve to their zero-padded canonical rows. Docs and examples updated from 25,000/28,000 references to the current 30,000+ figure.

[1.7.2] — 2026-07-16 (docs)

Documentation

  • Privacy sweep: removed vendor and tool names from prior release notes. Attribution values on image_meta.ownership are still returned by the API but no longer named in the public changelog. IFPS cultivar names (Fuji, Braeburn, Bartlett, Alkmene) stay — those are public reference terms.

[1.7.1] — 2026-07-16 (patch)

Catalog

  • Second produce photo batch imported. When a PLU already carried an image, the new upload was routed to an internal review queue rather than overwriting; admins pick which becomes the active image. Around 170 additional PLU codes now carry image_meta. Attribution strings on new rows are surfaced via the API in image_meta.ownership; the values themselves are set per-batch by the admin and can be updated later.

Internal

  • products.is_plu flag added to distinguish IFPS PLU codes from other short barcodes. Doesn't change the API surface.

[1.7.0] — 2026-07-15 (late evening)

Added

  • ~1,500 produce PLU codes imported from IFPS Global as first-class products. Each PLU (e.g. 3020 for Fuji apples, 4225 for Bartlett pears) is addressable through the normal GET /products/{barcode} endpoint. The barcode param accepts these 4–5 digit codes alongside 12–14 digit UPCs. Metadata: department="Produce", category = Fruits / Vegetables / Herbs / Nuts / Dried Fruits, subcategory = commodity name (e.g. APPLES), variety = cultivar (e.g. Braeburn).

  • New variety field on catalog_meta scope. Produce-only cultivar string (e.g. "Braeburn", "Alkmene", "Bartlett"). Returned as a top-level key on product_meta when include=catalog_meta (or the bundled product_meta). null for non-produce products.

    Field-lockable per API key like any other catalog_meta field.

Catalog

  • ~135 produce PLU codes are already image-associated (via filename match against the existing Produce_processed/ image library). More will be populated as the produce photo library grows.

[1.6.0] — 2026-07-15 (evening)

Added

  • Per-API-key field locks. Admins can now hide individual fields — or lock whole scopes — on a per-key basis from /admin/api-clients/{id}/field-locks. Two enforcement paths:

    • Whole scope locked (every field in info, nutrition, catalog_meta, etc.) → the API returns 403 scope_forbidden when the client's include= explicitly requests that scope. Response body includes blocked_scopes: [...] so the client can retry with a narrower include. No credits charged on 403.
    • Individual fields locked (e.g. info.brand, nutrition.calories, images.back_clean) → the scope still returns, but the locked fields are silently omitted from the payload. The response's meta.redacted_fields lists every dotted path that was removed (e.g. ["info.brand", "nutrition.total_sugars"]), so integrators can detect what's missing. Locked variants under images are also skipped for pricing — the client is not charged for URLs they can't receive.

    All 7 scopes are lockable: info, hechshers, nutrition, ingredients, catalog_meta, image_meta, images. hechshers is all-or-nothing (whole array); the others accept per-field granularity.

Documentation

  • OpenAPI: new ScopeForbidden response documented on GET /products/{barcode} with example body. ResponseMeta.redacted_fields documented as optional array of dotted paths.

[1.5.0] — 2026-07-15

Added

  • New catalog_meta include scope on GET /products/{barcode}. Returns merchandising fields not derived from the on-pack scan:

    • department — one of a controlled set of ~18 values (Appetizing, Baby & Bath, Beverages, Bread & Bakery, Chanukah, Dairy & Eggs, Frozen, Groceries, Health & Nutrition, Home & Leisure, Household & Cleaning, Kitchen Essentials, Nosh and Snacks, Passover, Produce, Purim, Sushi, Tishrei Holidays).
    • subcategory — free-text subcategory.
    • web_description — marketing copy suitable for a PDP.

    When a category or product_description override is set for the barcode, those values now appear in the top-level info.category and info.product_description fields (previously always sourced from the on-pack scan). Existing integrations that read those two fields continue to work — the shape and location are unchanged; only the source is broader.

  • Per-metadata-type pricing. Fine-grained scope rates are now published independently in GET /pricing:

    • products.get.info
    • products.get.hechshers
    • products.get.nutrition
    • products.get.ingredients
    • products.get.catalog_meta

    Legacy products.get remains — sending include=product_meta bundles all five fine-grained scopes at that single flat rate for back-compat. Fine-grained scopes usually add up to less than the bundle for realistic PDP reads — pick only the scopes you consume.

Changed

  • include= parameter accepts new values. info, hechshers, nutrition, ingredients, catalog_meta are now valid alongside the existing product_meta, image_meta, images. The old value set is unchanged and still supported. Omitting include= still returns everything (all metadata scopes + all image variants), same as before.
  • GET /pricing response now lists the 5 new fine-grained action keys under actions. Existing keys (products.get, products.get.per_image, products.get.image_meta, products.head, products.bulk_check.per_barcode, etc.) unchanged.
  • docs/pricing.mdx scope table — expanded to document all 5 fine-grained scopes + the legacy bundle + the two image scopes, each with its DB rate key.

Catalog

  • 28,000+ total products in the catalog (up from 25,000+ at launch), of which 6,943 are fully scanned on both sides. 9,832 barcodes now carry department / category / subcategory / web_description values from an initial third-party catalog import.

[1.4.0] — 2026-07-02 (evening)

Added

  • Idempotency-Key header on POST /products/bulk-check and POST /auth/revoke. Send a 20-255 char unique token to make retries safe. Replaying the same key + same body within 24 hours returns the cached response with Idempotency-Replayed: 1 header. Replaying with a different body returns 409 idempotency_key_mismatch. Recommended pattern: UUID per logical operation, or a stable per-day key for scheduled jobs.
  • updated_since query parameter on GET /products. Pass an ISO date/datetime to return only products whose updated_at is on or after that value. Combines with the existing search, min_digits, has_front, etc. filters. Ideal for incremental nightly catalog syncs — save the current time before your sync, use it as updated_since on the next run to fetch just the changes. See the POS integration guide for the full nightly-sync pattern.

Changed

  • Response headers now include X-Request-ID, X-Credits-Debited, X-Credits-Remaining, X-RateLimit-Limit-Minute/-Day, X-RateLimit-Remaining-Minute/-Day, X-RateLimit-Reset, and Content-Type: application/json; charset=utf-8 on every /api/v1/* response. Documented in the top-level info.description of the OpenAPI spec.
  • source_batch field on image_meta is now documented as open string (was enum leaking internal ops tags into public contract).

[1.3.0] — 2026-07-02 (afternoon)

Changed

  • GET /pricing now exposes the cost model explicitly. Response now includes pricing_model, formula, notes, and per-action descriptions alongside the existing actions map. The formula credits = (per_scope_rate × metadata_scopes) + (per_image_rate × image_variants) is now discoverable from a single API call — customers no longer need to reverse-engineer it from meta.credits_debited responses.

    Backwards compatible: the actions map keys and values are unchanged. Existing daemons that read actions["products.get"] and actions["products.get.per_image"] keep working; new integrators can additionally read formula / notes / descriptions to learn the model without doing arithmetic.

Documentation

  • include= parameter docs — rewritten to state the per-scope cost, explicitly call out the "omit → all three scopes billed" default, and cross-reference variants=. Old copy said only "Omit for all three" without any pricing consequence.
  • variants= parameter docs — rewritten to state that combining with include= is required for the cheapest call. Old copy left include= implicit.
  • docs/pricing.mdx cost formula callout — prominent :::tip at the top of the page shows the formula, the "cheapest for common use cases" ladder, and the double-set-both-parameters landmine that our first live customer integration hit.
  • docs/getting-started.mdx Step 4/5 flow — Step 4 now warns that the no-params default is the most expensive call. Step 5 introduces the two cost levers with a :::danger box on the "set BOTH" requirement.
  • docs/reference/product-metadata.mdx — two separate cost tables merged into one 9-row interaction matrix showing every include= × variants= combination.

[1.2.0] — 2026-07-02 (morning)

Added

  • variants= query parameter on GET /products/{barcode} — comma-separated list of image variants (front, back, front_clean, back_clean) the customer actually wants URLs for. When omitted, every available variant is returned (unchanged behavior). Pricing is per requested variant — asking for variants=front on a 4-variant product now costs 1 credit instead of 4. Invalid values return 400 bad_request.

    Backwards-compatible: integrations that don't send variants= continue to receive every variant and pay for every URL, as before.

Changed

  • The admin activity log at /admin/api-clients now surfaces the include= and variants= values each customer sent per request (stored in a new api_usage.include_scopes column). Operators can verify what the customer asked for without doing pricing arithmetic.

[1.1.0] — 2026-07-02

Added

  • GET /balance — minimal-payload shortcut for polling credit balance. Free, returns { client_id, credit_balance, tier, meta }. Use it for dashboard widgets, low-balance alerts, or pre-flight checks before an expensive call. See the API reference for the schema.

Changed

  • GET /products/{barcode} response now matches the documented schema:
    • hechshers[] fields renamed from hechsher_symbol / hechsher_org / hechsher_logo to symbol / org / logo (matches the Product metadata reference)
    • nutrition.<field> are now { amount, dv } objects instead of the parallel flat total_fat + total_fat_dv scalars the DB stored
    • ingredients.allergens is a real JSON array (was a JSON-encoded string)
    • kosher_for_passover, gluten_free, vegan, organic return real booleans (were 0 / 1 integers)
    • Internal DB columns (id, created_at, updated_at, redundant barcode) stripped from every nested object

Migration note. Existing integrations that decoded allergens with JSON.parse() should just consume it as an array now. Field renames in hechshers[] are breaking if you switched on the old prefixed names — the payload only matched the documented spec starting with this release, so treat this as the "spec-compliant" baseline going forward.

API infrastructure

  • Per-endpoint throttle on POST /auth/token and POST /auth/refresh10 requests per minute per IP (below the global 60/min for authenticated routes). Brute-forcing a raw api_key was already impractical at the client's 48-char random key length; this tightens the layer at zero cost to legitimate integrations.
  • Per-user cap on the number of active API clients (default 5, admin-configurable)
  • New self-issued clients start at 0 credits (was 100). Admin approves the initial balance top-up. Prevents credit farming via bulk client creation.
  • All API-delivered images now carry the same admin-controlled watermark used for ZIP downloads (was placeholder before). Response advertises X-Watermarked: 0/1.

[1.0.0] — Initial public release

Public launch of the Retail Digitals Image API.

Endpoints

  • POST /auth/token — exchange client_id + api_key for access + refresh tokens
  • POST /auth/refresh — rotate refresh token, get new access token
  • POST /auth/revoke — explicitly invalidate current tokens
  • GET /products/{barcode} — full product with image_meta, product_meta, images
  • HEAD /products/{barcode} — existence check
  • GET /products — paginated catalog listing with filters
  • POST /products/bulk-check — batch existence check, up to 500 barcodes
  • GET /products/{barcode}/image?variant= — 302 redirect to signed image URL
  • GET /image/{token} — signed URL delivery endpoint
  • GET /account — balance + quotas + usage snapshot
  • GET /usage — historical usage query
  • GET /health — service health
  • GET /pricing — current per-action credit prices

Auth

  • HS256-signed JWT access tokens (1 hour TTL)
  • Single-use refresh tokens with reuse detection (90-day TTL, rotated on every use)
  • Bearer scheme (Authorization: Bearer <access_token>)
  • Per-endpoint rate limits on /auth/token (10/min/IP) and /auth/refresh (30/min/IP)

Security

  • All signed image URLs are IP-bound, 15-minute TTL, single-use
  • Every delivered image carries visible + invisible per-client watermarks
  • Optional per-client IP allowlist
  • 90-day audit log queryable via /usage

Catalog

  • 25,000+ kosher CPG products at launch
  • Up to 4 image variants per product: front, back, front_clean, back_clean
  • Full structured metadata: brand, ingredients, allergens, hechshers, nutrition, bracha, etc.
  • product_meta schema documented in Product Metadata Reference

Pricing

  • Initial pricing table published — see Pricing
  • Standard tier: pay-as-you-go, no monthly base
  • Enterprise: contact sales

Known limitations

  • Response bodies uncompressed (gzip planned for 1.1.0)
  • No WebHook events (planned)
  • No mobile SDKs (roadmap Q4 2026)
  • Some products still awaiting full nutrition scan — see product_meta.nutrition for null

Version support policy

VersionStatusSupported until
1.xCurrent

All 1.x releases are additive minor bumps on the same /api/v1/* surface — no breaking changes, no parallel branches. Integrations written against 1.0.0 keep working against 1.6.0 without changes.

We commit to supporting each MAJOR version for at least 12 months after its successor ships. During the overlap, both versions run on the same infrastructure with the same uptime SLA.