Changelog
All notable changes to the Retail Digitals Image API. We follow
Semantic Versioning — MAJOR.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 checkedGET /metadata/{barcode}first. Theproduct_metaflat 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-checkand the metadata-availability endpoints are unaffected.
[1.8.1] — 2026-08-10
Fixed
POST /products/bulk-checknow 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 a301, 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 return200. - Malformed requests always return JSON. A request that omitted
Accept: application/jsonpreviously got a302redirect to an HTML page on a validation failure, instead of a JSON422— 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, plusworth_requesting: the exactinclude=scopes that would return data for it.POST /metadata/bulk-check— the same for up to 500 barcodes per call. Sendfields: truefor per-field detail. SupportsIdempotency-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, somin_digits=5and above silently dropped the entire produce range fromGET /products. PLUs are now exempt from that filter.max_digitsis 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.ownershipare 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 inimage_meta.ownership; the values themselves are set per-batch by the admin and can be updated later.
Internal
products.is_pluflag 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.
3020for Fuji apples,4225for Bartlett pears) is addressable through the normalGET /products/{barcode}endpoint. Thebarcodeparam 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
varietyfield oncatalog_metascope. Produce-only cultivar string (e.g."Braeburn","Alkmene","Bartlett"). Returned as a top-level key onproduct_metawheninclude=catalog_meta(or the bundledproduct_meta).nullfor 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 returns403 scope_forbiddenwhen the client'sinclude=explicitly requests that scope. Response body includesblocked_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'smeta.redacted_fieldslists every dotted path that was removed (e.g.["info.brand", "nutrition.total_sugars"]), so integrators can detect what's missing. Locked variants underimagesare 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.hechshersis all-or-nothing (whole array); the others accept per-field granularity. - Whole scope locked (every field in
Documentation
- OpenAPI: new
ScopeForbiddenresponse documented onGET /products/{barcode}with example body.ResponseMeta.redacted_fieldsdocumented as optional array of dotted paths.
[1.5.0] — 2026-07-15
Added
-
New
catalog_metainclude scope onGET /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
categoryorproduct_descriptionoverride is set for the barcode, those values now appear in the top-levelinfo.categoryandinfo.product_descriptionfields (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.infoproducts.get.hechshersproducts.get.nutritionproducts.get.ingredientsproducts.get.catalog_meta
Legacy
products.getremains — sendinginclude=product_metabundles 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_metaare now valid alongside the existingproduct_meta,image_meta,images. The old value set is unchanged and still supported. Omittinginclude=still returns everything (all metadata scopes + all image variants), same as before.GET /pricingresponse now lists the 5 new fine-grained action keys underactions. Existing keys (products.get,products.get.per_image,products.get.image_meta,products.head,products.bulk_check.per_barcode, etc.) unchanged.docs/pricing.mdxscope 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_descriptionvalues from an initial third-party catalog import.
[1.4.0] — 2026-07-02 (evening)
Added
Idempotency-Keyheader onPOST /products/bulk-checkandPOST /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 withIdempotency-Replayed: 1header. Replaying with a different body returns409 idempotency_key_mismatch. Recommended pattern: UUID per logical operation, or a stable per-day key for scheduled jobs.updated_sincequery parameter onGET /products. Pass an ISO date/datetime to return only products whoseupdated_atis on or after that value. Combines with the existingsearch,min_digits,has_front, etc. filters. Ideal for incremental nightly catalog syncs — save the current time before your sync, use it asupdated_sinceon 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, andContent-Type: application/json; charset=utf-8on every/api/v1/*response. Documented in the top-levelinfo.descriptionof the OpenAPI spec. source_batchfield onimage_metais now documented as open string (was enum leaking internal ops tags into public contract).
[1.3.0] — 2026-07-02 (afternoon)
Changed
-
GET /pricingnow exposes the cost model explicitly. Response now includespricing_model,formula,notes, and per-actiondescriptionsalongside the existingactionsmap. The formulacredits = (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 frommeta.credits_debitedresponses.Backwards compatible: the
actionsmap keys and values are unchanged. Existing daemons that readactions["products.get"]andactions["products.get.per_image"]keep working; new integrators can additionally readformula/notes/descriptionsto 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-referencevariants=. Old copy said only "Omit for all three" without any pricing consequence.variants=parameter docs — rewritten to state that combining withinclude=is required for the cheapest call. Old copy leftinclude=implicit.docs/pricing.mdxcost formula callout — prominent:::tipat 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.mdxStep 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:::dangerbox on the "set BOTH" requirement.docs/reference/product-metadata.mdx— two separate cost tables merged into one 9-row interaction matrix showing everyinclude=×variants=combination.
[1.2.0] — 2026-07-02 (morning)
Added
-
variants=query parameter onGET /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 forvariants=fronton a 4-variant product now costs 1 credit instead of 4. Invalid values return400 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-clientsnow surfaces theinclude=andvariants=values each customer sent per request (stored in a newapi_usage.include_scopescolumn). 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 fromhechsher_symbol/hechsher_org/hechsher_logotosymbol/org/logo(matches the Product metadata reference)nutrition.<field>are now{ amount, dv }objects instead of the parallel flattotal_fat+total_fat_dvscalars the DB storedingredients.allergensis a real JSON array (was a JSON-encoded string)kosher_for_passover,gluten_free,vegan,organicreturn real booleans (were 0 / 1 integers)- Internal DB columns (
id,created_at,updated_at, redundantbarcode) 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/tokenandPOST /auth/refresh— 10 requests per minute per IP (below the global 60/min for authenticated routes). Brute-forcing a rawapi_keywas 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— exchangeclient_id + api_keyfor access + refresh tokensPOST /auth/refresh— rotate refresh token, get new access tokenPOST /auth/revoke— explicitly invalidate current tokensGET /products/{barcode}— full product withimage_meta,product_meta,imagesHEAD /products/{barcode}— existence checkGET /products— paginated catalog listing with filtersPOST /products/bulk-check— batch existence check, up to 500 barcodesGET /products/{barcode}/image?variant=— 302 redirect to signed image URLGET /image/{token}— signed URL delivery endpointGET /account— balance + quotas + usage snapshotGET /usage— historical usage queryGET /health— service healthGET /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_metaschema 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.nutritionfor null
Version support policy
| Version | Status | Supported until |
|---|---|---|
| 1.x | Current | — |
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.