Private beta This page previews the developer API we're building. There's no self-serve signup yet: request access to get a key.

The write API for a grocery account.

Every call here runs against a real, authenticated grocery account, the same one a person signs into by hand, not a catalog scraped from the outside. The primitives follow from that: Composio's ergonomics for minting a connect link, Plaid's for learning what happened, and a signed webhook instead of a redirect the user can quietly abandon.

Overview

Grocey is the write API for a person's own grocery account. There is a catalog you can read too, but it is the byproduct, not the product: search and stores answer against the store someone actually shops at, cart writes to their real basket, and, where the provider supports it, orders returns their real order history. Nothing here is scraped from the outside or pulled from a partner feed; it drives the actual provider site in an isolated browser session, signed in the same way the person would sign in by hand.

If you just want to connect your own store to Claude or ChatGPT and you are not writing code, this page is not for you: go to Grocey for assistants instead. This page is the technical reference for builders. Grocey for developers is the shorter pitch and setup walkthrough, and your dashboard runs the real connect flow against a real account with nothing to install.

Today Grocey runs in production as a first-party substrate with one caller: it's what powers grocery accounts inside Alfredo, our own concierge agent. This page documents the public developer platform being built on top of that substrate: your own gro_* key, your own redirect, a signed webhook when a user connects, and the same account plane over MCP.

Two planes

The developer plane, both api.grocey.shop/v1/* and POST /mcp, is authenticated with your gro_* key end to end: there is no anonymous route, including the provider directory. The end-user connect page (link.grocey.shop/c) is a different credential: a signed, single-use link nonce, never your key. Holding the link gets a user their own account and no one else's. Grocey for assistants is that same end-user plane with a page in front of it, for someone who would rather not read this section at all.

Authentication

Every developer-plane request will carry Authorization: Bearer gro_live_… (or gro_test_… against the sandbox). Keys are shown once at creation and stored only as a hash, the same mechanism the rest of Loki Labs' platform already uses for API keys, not a shared secret every developer holds the same copy of.

preview · private beta
# every developer-plane request
curl https://api.grocey.shop/v1/providers?country=PL \
  -H "Authorization: Bearer gro_live_…"

Webhooks

The redirect is a UX affordance the user can abandon; the webhook is the record. Every payload will be signed: Grocey-Signature: t={timestamp},v1={hex}, HMAC-SHA256 over {timestamp}.{raw body}, Stripe's scheme, because it's the one most backends already have library code for. Delivery is at-least-once, so event_id is the idempotency key, and ordering isn't guaranteed across retries.

Sign over the raw request body, before any JSON parsing. Re-serializing first is the most common signature-verification bug.

Sandbox

A gro_test_ key will reach one deterministic fixture provider, so you can build and test a full integration before a country you need is covered for real. The sandbox provider is planned, not shipped. It will never appear in the coverage table below, and it will never stand in as an oracle for whether a real provider works.

Catalog API

Two read endpoints over the crawled catalogue, on the same gro_* key as everything else. They are not scoped to a connection or to an app, because a shop's shelf is the same for everyone: what one app crawls, every app reads.

provider is required. store_id, category_id and q narrow the result, limit defaults to 50 and refuses anything over 200, and paging is a cursor rather than an offset, so a crawl landing mid-page cannot make you skip a product.

Prices are minor units plus a currency code, never a float, and price_minor is null for an item the store does not price on its own listing. That is a real answer, not a missing one: reading it as zero would invent a free product. Categories come back with their provider id and a product count, and name is currently always null: only a live call against a signed-in account knows the human name of a category, and this endpoint reads D1, not the store.

Coverage is exactly the stores Grocey crawls, listed below. A store with no crawl yet returns an empty page, not an error.

live · captured from a real call
GET https://api.grocey.shop/v1/catalog/products?provider=delio&q=mleko&limit=2

 {
  "products": [
    {
      "sku": "A0000709",
      "name": "Łaciate mleko UHT 2%",
      "price_minor": 399,
      "currency": "PLN",
      "available": true,
      "image_url": "https://images.prod.lait.app/…",
      "category_id": "3d2dedb1-0031-4223-aadb-65b1f256fa51",
      "description": null
    }
  ],
  // null on the last page
  "next_cursor": "eyJzIjoiZGVsaW8iLCJr…"
}

GET https://api.grocey.shop/v1/catalog/categories?provider=delio

 { "categories": [ { "id": "3d2dedb1-0031-4223-aadb-65b1f256fa51", "name": null, "product_count": 1001 } ] }

Coverage

This is the one section on this page that's true right now, not a preview: generated at build time from the exact same provider declarations the API will read.

Every provider below also has its own page, generated from this same data and linked from the footer: see the full integrations index for search terms and per-store sign-in details.

1 provider live today 4 more are built and unit-tested, not yet connected to a real account.
Collect&Go BE 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 PL Live

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

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

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

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

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

✓ Browse & fulfilment ✓ Cart ✓ Order history
Wolt PL Building

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

✓ Browse & fulfilment ✓ Cart ✓ Order history

What Grocey doesn't do

Payment is off by default, and here is what would have to be true. grocery_checkout is a real tool on this API, not a doctrine sentence promising one doesn't exist. Whether a call to it can ever succeed is decided by four independent guards, enforced in code rather than in a tool description:

  • No connected provider implements it. Delio, Frisco.pl and Wolt each define no checkout method, so the call refuses before anything else runs. This is the guard doing the work today.
  • GROCEY_CHECKOUT must be set to exactly "enabled" in the deployment. Off by default, and only the owner can turn it on.
  • The person must already have a payment card saved at the provider, themselves. Grocey never collects card details, so an account with none saved cannot be checked out, flag or no flag.
  • A server-minted token, bound to the exact basket at the moment it was priced, has to come back unchanged. A model can neither mint one nor edit one, and it stops verifying the instant the basket does.

None of that is a roadmap promise, and none of it is "unavailable, full stop": the mechanism is real and merged. What's missing is a provider that implements it and a switch anyone has turned on. Placing an order stays a human's tap on the provider's own site until that changes.

No favourite dishes. A provider hands back products identified by their own SKU, at their own store; nothing in that shape is a dish. A dish is an inference over a basket, something we would have to generate rather than fetch, and doing that behind an API whose whole pitch is "this is your real account" is the wrong seam: it would blur what actually came from the account against what we made up about it.

No published pricing. Cost here scales with the number of connections held open, not the number of calls made, and the concurrency ceiling that would set a sane price hasn't been measured yet. A number picked ahead of that measurement would be a guess wearing a price tag.