Start here
A key in one call, no email round-trip.
# A one-hour tryout key in one call:
curl -X POST https://primat.nu/api/v3/signup \
-H 'Content-Type: application/json' \
-d '{"email": "you@example.com"}'
# Which stores serve a place? The site's own resolver:
curl -H 'Authorization: Bearer <key>' \
'https://primat.nu/api/v3/stores/resolve?place=Uppsala'
# Prices for a product at those stores:
curl -H 'Authorization: Bearer <key>' \
'https://primat.nu/api/v3/products?q=banan&stores=ica:1003385'
The tryout key works for one hour on a tight quota. Ongoing access starts with a 30-day free App trial (card required; 249 SEK/month ex VAT after the trial, cancel anytime): log in at primat.nu/data with the same address, and the key keeps working once the trial starts. Machine-readable contract: https://primat.nu/api/v3/openapi.json — self-describing root: /api/v3.
Authentication
Authorization: Bearer <key> or
X-API-Key: <key>. Two key types:
secret (primat_live_…, server-side) and
publishable (primat_pk_…, safe to embed
in client-side apps: locked to your web origins, read-only, own
quota, CORS enabled). Manage keys at
primat.nu/konto.
Endpoints
v3 — the current surface. v1/v2 keep serving frozen for existing integrations.
-
GET /api/v3no key -
GET /api/v3/openapi.jsonno key -
POST /api/v3/signupno key -
POST /api/v3/confirm/request -
GET /api/v3/me -
GET /api/v3/stores -
GET /api/v3/stores/resolve -
GET /api/v3/products -
GET /api/v3/products/{chain}/{store_id}/{product_id} -
GET /api/v3/prices -
GET /api/v3/deals -
GET /api/v3/cheap -
POST /api/v3/batch -
GET /api/v3/history/{chain}/{store_id}/{product_id} -
GET /api/v3/history/{gtin}
Price semantics
Straight from the contract — the same text agents read in the OpenAPI document.
Price FACTS. regular: what everyone pays (public campaigns included). member: only set while a member price runs. multiprice/member_multiprice: PER-UNIT price when buying `quantity` (7 with quantity 5 = '5 for 35 kr'). comparison: unit price per kg/l/st computed on the REGULAR price. offer: the active campaign behind the current pricing, null when none. effective: least price a card-carrying shopper pays right now (a convenience fold; combine the facts yourself for anything serious). previous: on /deals results.
Fields: regular, member, multiprice, member_multiprice, comparison, offer, effective, previous.
Timestamps: changed_at = when the price last CHANGED,
confirmed_at = when the store was last checked. No
change since changed_at means confirmed unchanged, not
unmeasured.
Plans
Prices ex VAT. Sign up and upgrade at primat.nu/data.
| Plan | Price | Rate | Rows | History | Image URLs |
|---|---|---|---|---|---|
| App | 249 SEK/mo | 250/min (+250 burst) | 100,000 rows/day | — | yes |
| Pro | 1995 SEK/mo | 600/min (+600 burst) | 1,000,000 rows/day | 12 mo | yes |
| Byrå | 7495 SEK/mo | 3000/min (+3000 burst) | unlimited | full | yes |
Errors
Every non-2xx answer is
{"error": {"code", "message"}} with honest
Retry-After/WWW-Authenticate headers.
Codes: invalid_api_key, rate_limited, daily_row_budget_exceeded, query_required, bad_request, not_found, product_not_found, place_not_found, upgrade_required, trial_required, tryout_expired, tryout_used, origin_not_allowed, read_only_key, email_in_use, invalid_email, signup_limit_reached, history_timeout, service_unavailable.
Questions, bulk exports (CSV/Parquet, Byrå plan), or something missing? kontakt@primat.nu.