Private beta · built for developers

Not a grocery catalog API. A write API for one real account.

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.

profile · preview, private beta
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

One key for your app. One cookie for your user.

Grocey answers two different questions, and never lets one credential answer both.

The developer plane

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.

The end-user plane

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

Mint a link. The user signs in. You get an id back.

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.

connect_links · preview, private beta
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

The redirect is a UX affordance. The webhook is the record.

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

Every provider answers the same shape.

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.

1 provider live today 4 more are built and unit-tested, not yet connected to a real account.
Collect&Go Building

Colruyt's Belgian & Luxembourg click-and-collect grocery service. Sign in with your Collect&Go e-mail and password.

✕ Browse & fulfilment ✓ Cart ✓ Order history
Delio Live

Polish grocery delivery. Sign in with your phone number and an SMS code.

✓ Browse & fulfilment ✓ Cart ✓ Order history
Frisco.pl Building

Polish online supermarket with van delivery. Sign in with your e-mail and password.

✓ Browse & fulfilment ✓ Cart ✓ Order history
Żabka Jush Building

Żabka's express grocery delivery in Poland. Same account and sign-in as Delio.

✓ Browse & fulfilment ✓ Cart ✓ Order history
Wolt Building

Groceries and shops on Wolt. Sign in with the link Wolt mails to your address.

✓ Browse & fulfilment ✓ Cart ✓ Order history

A full, per-method breakdown lives on each provider's own page in the integrations directory.

The one action we don't automate

There is no order-placement tool.

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

Private beta, named developers.

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

The questions builders actually ask.

Is there a sandbox?

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.

Which providers actually work today?

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.

Does Grocey place orders on someone's behalf?

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.

Can I call this from Claude or ChatGPT?

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.

How is this different from scraping or a public product catalog?

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.

What would I actually build with this?

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.

How do I get access?

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.

Building an AI grocery agent that needs a real account?

Grocey is private beta. Tell us what you are building and we will set you up with a key.