Point Grocey at your user. They sign in once, to their own store, in an isolated browser session. You get back their real order history, their real delivery addresses and slots, and a cart you can write to: the one thing a public catalog or a scraper can never hand you, because reading a person's account means being authenticated as them.
GET https://api.grocey.shop/v1/connections/cxn_a1b2c3/profile Authorization: Bearer gro_live_… → 200 { "profile": { "status": "ready", "orders": { "supported": true, "value": [ {…} ] }, "addresses": { "supported": true, "value": [ {…} ] }, "favourites": { "supported": false } } }
Two credentials, on purpose
Grocey answers two different questions, and never lets one credential answer both.
api.grocey.shop/v1/* · POST /mcp
Authorization: Bearer gro_live_… (or gro_test_…)
Your server, talking to Grocey as your app. Every request carries your key, with no anonymous route anywhere on this plane, including the provider directory.
link.grocey.shop/c · /api/session*
a signed, single-use link nonce, plus the gr_owner cookie
The person proving they own the account they are about to connect. Their browser never sees or holds your API key, and your key never has to leave your server to reach one.
The split is not incidental. A public catalog API only ever has to answer one question: is this app allowed to ask. A grocery integration API that reads someone's real orders has to answer a second one too: is this person who they say they are, and only their own sign-in can answer that. Collapsing the two would mean shipping your server secret to a browser, or making every one of your users register for a Grocey account just to prove who they are, twice.
How a user actually connects
No SDK to embed, and none of their credentials ever pass through your servers.
Call POST /v1/connect_links with your own redirect_url and an end_user_id. An https target is registered to your app ahead of
time; a custom app scheme like yourapp://return is
not, because the results come back over the API and the
webhook, not the redirect, so there is nothing worth stealing
on that URL. Either way, redirect_url is read from
the mint call and never from the page a user lands on, which is
what keeps a connect link from becoming an open redirect on the
one flow where that would matter most.
The user opens the link, sees what you are asking for, picks a provider (Delio, Frisco.pl or Wolt today) and signs in the same way they would on that store's own site.
Three things can happen next. The user connects, and your
redirect gets a connection_id. The user closes the
modal, in which case nothing was attempted, so no webhook fires
and calling it a failure would be wrong. Or a real failure
happens, shown in the modal with the return offered as a
button, never as an automatic redirect, because that is the
one moment the explanation matters. Treat a browser that never
comes back the same as a cancel: a browser you do not control
may simply never return. The webhook is what actually tells
you a connection exists.
POST https://api.grocey.shop/v1/connect_links { "end_user_id": "usr_42", "redirect_url": "https://your-app.example/return", "state": "whatever-you-need-back" } → { "connect_link_id": "cl_9f2k…", "url": "https://link.grocey.shop/c/…" } # the redirect, on success https://your-app.example/return?status=connected &connection_id=cxn_a1b2c3&state=whatever-you-need-back
The signal you can actually build on
Borrowed on purpose from Plaid: a browser you do not control can vanish mid-flow, so nothing about whether a connection exists should depend on it coming back.
A developer who only reads the redirect learns about the connects that finish cleanly, and nothing else. Someone who closes the tab, loses an SMS code, or gets stuck on a store's own sign-in page leaves no redirect at all, and those are exactly the accounts you most need to know about. The webhook fires from Grocey's own server the moment something happens, whether or not a browser is still open to hear about it.
Every payload is signed: Grocey-Signature: t={timestamp},v1={hex},
HMAC-SHA256 over {timestamp}.{raw body},
Stripe's own scheme, because it is the one most backends already
carry library code for. Delivery is at-least-once, so event_id is the idempotency key, and ordering is not
guaranteed across retries: up to 8 attempts with exponential
backoff and jitter, spread over roughly 24 hours, before a
delivery is marked exhausted. GET /v1/events lists
every delivery, so a missed one is self-serve rather than a
support ticket, and POST /v1/events/{id}/replay sends the exact
same payload again under a fresh signature.
connection.created Fires today A sign-in verified. Carries the new connection_id.
connection.enriched Fires today The account's first read of orders, addresses, favourites and spend patterns finished. Fires after connection.created, on its own schedule, never before it.
connection.failed Fires today A connect attempt ended without a working session.
connection.deleted Fires today The developer or the end user removed the connection.
connection.stale Declared, not yet fired In the contract. A session that cannot be refreshed marks the connection stale internally today, so a read answers reconnect this account instead of failing oddly, but that state change does not fire the webhook yet.
connection.revoked Declared, not yet fired In the contract. Nothing in the runtime today tells a provider actively revoking access apart from an ordinary expired session, so this event has no trigger point yet.
connection.stale and connection.revoked are
real, declared event types. Neither fires from the runtime yet,
and this page says so rather than letting the contract imply
more than the code does.
One contract, 15 methods, 5 providers
getFulfilment, search, cart, orders, addToCart, and more: the same
methods, the same types, whichever store you connect. The list
below is data a conformance test walks, not a promise in prose.
getFulfilmentfulfilmentOptionssetFulfilmentstoressearchcategoriescatalogPageordersorderDetailscartaddToCartremoveFromCartclearCartsuggestReplacementscartUrl
Plus 2 optional methods no provider
implements yet: paymentMethods , checkout (see "What Grocey doesn't do" below).
A supermarket API that only reads a catalog does not need this
discipline. One that writes to someone's real cart does: GET /v1/providers returns this exact matrix, derived
from the same declarations the API itself reads, so this page can
never claim a capability the code does not have.
Colruyt's Belgian & Luxembourg click-and-collect grocery service. Sign in with your Collect&Go e-mail and password.
Polish grocery delivery. Sign in with your phone number and an SMS code.
Polish online supermarket with van delivery. Sign in with your e-mail and password.
Żabka's express grocery delivery in Poland. Same account and sign-in as Delio.
Groceries and shops on Wolt. Sign in with the link Wolt mails to your address.
A full, per-method breakdown lives on each provider's own page in the integrations directory.
The one action we don't automate
Grocey stops the surface before the point where money moves, on purpose. checkout exists in the contract as an optional method, most providers don't have one at all, and placing an order through it needs three independent conditions to hold at once: an environment flag the owner turns on per deployment, a saved payment instrument the account holder saved themselves (nobody has one today, on any connected provider), and a server-minted token bound to the exact basket being confirmed.
A model cannot talk its way past any of the three. Opening a paid checkout to third-party developers over credentials we custody is a separate decision with its own review, and it isn't made by omission here.
That is the shape of an agentic commerce API done honestly: a real account, real actions, and one hard stop the whole contract is built around.
Getting in
There's no self-serve signup live yet. Grocey is private beta today because opening it publicly needs two things that aren't finished: a legal read of each provider's terms of service, and a GDPR answer for driving someone else's account on their behalf. Access is granted case by case, to named developers, until both close.
When pricing exists, it will be priced per connection, not per request: a connect is a live, isolated browser session against a real store, not a cheap API call to cache and forget. There's no number yet, and there won't be one until the concurrency ceiling behind it has actually been measured.
FAQ
A gro_test_ key will reach one deterministic fixture provider, so an integration can be built and tested before a country you need is covered for real. It's planned, not shipped: it will never appear in the coverage table, and it will never stand in as proof a real provider works.
Delio has completed a real connect. Collect&Go and Frisco.pl and Żabka Jush and Wolt are built and unit-tested against the real contract, but neither has ever connected to a live account, so this page labels them Building, not Live.
No. Search, cart, fulfilment and order history are what's live. checkout exists in the contract as an optional method, gated off by default, and nothing in production has ever placed a real order through it.
Yes. Grocey also runs as an MCP grocery server: POST /mcp speaks the same contract as the REST API, so an MCP-speaking agent calls grocery_search, grocery_cart, grocery_add_to_cart and the rest directly, with the same gro_* key.
Grocey signs in as the real account, in an isolated browser session, the same way the account holder would by hand. There's no public catalog to scrape and no partner supermarket API to wait on, which is also why every read is scoped to one person's own account rather than a shared, generic one.
An AI grocery agent that fills a cart from a recipe or a week of meals. A reorder assistant that watches an order history for a running-low pattern and asks before it adds anything. A price or availability check across every store a person has connected, using their own account instead of a public catalog that's already out of date the moment a store's stock changes.
There's no self-serve signup live yet. Request access and we'll set you up with a key by hand, scoped to what you told us you're building.
Grocey is private beta. Tell us what you are building and we will set you up with a key.