Better Beans public data API

Version 1.0 provides public directory data. Better Beans is the publisher; each roaster is the seller. Unknown fields are null, not guessed facts. The API is read-only and needs no account.

OpenAPI specification · JSON Schema · Methodology

Start with a small page

curl "https://betterbeans.com/api/v1/products?limit=2"
curl "https://betterbeans.com/api/v1/roasters?state=CA&limit=2"
curl "https://betterbeans.com/api/v1/facets"

Follow pagination.next or pass next_cursor with the same filters. The limit is 1–50 entities, default 20. Products contain nested variants; pagination.total counts products, never bag sizes. Roaster totals count organizations.

Cursors use stable UUID order. Changing filters requires a new traversal. The directory is live, not a frozen export: concurrent inserts, publication changes, and corrections can change totals. Reconcile by stable ID and restart a traversal when you need an updated snapshot.

Synthetic example response — demonstrates unknown currency and source dates; it is not a real listing.

Filters and identities

Products support id, variant_id, roaster_id, origin, roast, type, decaf, and available. Origin and roast use versioned normalization of catalog labels; normalization is not independent verification. Roasters support id and a two-letter state code. The facets endpoint lists accepted values and qualified hubs, not live facet counts.

The product ID groups a coffee’s variants. Variant IDs preserve the older coffee-row UUIDs. canonical_url points to Better Beans; seller_url points to the official seller. Price filters, budget queries, and price sorting are not part of v1.

Prices, quantities, and evidence

Amounts and unit weights are decimal strings. unit_weight_grams describes one package; pack_count is separate. Currency remains null when unverified. price.observed_at identifies a source price check, while content_updated_at tracks content changes. A source-observed price does not verify every descriptive field.

listing_available is the directory’s current listing state, not a live stock guarantee. Purchase type is one_time only for the verified current-price path; otherwise it is unknown. Shipping, tax, discounts, and final availability must be checked at the seller. Archived informational pages can remain in the API; removed, redirected and quarantined identities are excluded.

facts retains catalog labels. normalized explicitly remains legacy_unverified. approved_facts contains independently reviewed values with public source metadata. Approved location and capability records are separate from company descriptions; an empty array means no approved records, not that the service or location does not exist.

Caching, limits, and errors

Use ETag and If-None-Match for unchanged responses (304). Responses can be cached for one minute, with another minute of stale-while-revalidate. Up to 60 uncached origin reads per client per minute are allowed; shared networks may share a quota. A 429 includes Retry-After: 60. Invalid or repeated parameters return 400. Temporary failures return 503; retry with backoff or a smaller page.

Rate protection stores a keyed daily hash, not raw client IPs, and removes counters older than one day. Existing infrastructure access logs follow the site’s privacy policy. No private contacts, research packets, reviewer identities, or internal notes are part of the API.

Dated price history

GET /api/v1/variants/{id}/history returns validated source observations, with optional from and to dates (YYYY-MM-DD, inclusive UTC days). The default is 90 days and the maximum interval is 366 days. Up to 500 latest points are returned in chronological order; truncated=true means you should request narrower intervals. No prior price is inferred for missing days.

Current correction revisions replace prior observations; revision and corrects_observation_id preserve traceability. Retracted and quarantined revisions are excluded. series_key separates purchase conditions and product_version_key marks source identity changes; these opaque values are not seller identifiers. Quantity fields are historical snapshots. History JSON Schema.

Compatibility and crawler policy

Existing /api/coffees and /api/roasters endpoints remain available. Additive v1 fields may be introduced; incompatible changes require another version. Public discovery resources are linked from llms.txt and robots.txt. Access does not imply that any particular search or AI service has crawled, indexed, or endorsed the directory. Search crawling and model-training policy are separate owner decisions.

Report a data issue.