Shop contract
storefront-contract v3: the shop's backend is the @delieta/shop-runtime package, two thin shims, a publishable key on the server side, signed refresh.
The shop contract (storefront-contract@v3) describes a shop on Delieta. The shop's backend is the npm package @delieta/shop-runtime (version 1.6.0), not files in the shop's repository. The shop repository holds the look and two thin shims. The rest (Partner API access, cart, checkout, refresh) comes from the package, so a runtime fix reaches every shop with one release.
Four env values
DELIETA_API_URL=https://delieta.pl
DELIETA_PUBLISHABLE_KEY=dlt_live_…
DELIETA_REVALIDATE_SECRET=…
DELIETA_DATA_MODE=live
DELIETA_API_URL is the origin alone: no /api/v1, no trailing slash. DELIETA_PUBLISHABLE_KEY is the catalog:read + checkout:create key. DELIETA_REVALIDATE_SECRET signs the refresh signal and verifies preview links on a shop on sample data. DELIETA_DATA_MODE is optional: sample or live, unset means live, and the build rejects any other value. None of them has a NEXT_PUBLIC_ prefix.
No env → a "configure the shop" page, not an empty site pretending to be a shop. Env is set but Delieta does not answer → a "products unavailable" page, not a menu with zero items that looks like a deployment.
Publishable key: server side only
The publishable key is exactly catalog:read + checkout:create. It can create a cart, so it must not reach the browser. The runtime keeps it on the server side and sends it itself. After the build delieta-shop-assert aborts if the key leaked into client JS code. A secret key (orders:write, eta:quote, reservations:write, catering:write) goes neither into the shop nor into its repository. dlt_test_… is not a safe secret in HTML either.
The key identifies the company. Do not send companyId.
Three areas
| Area | Where from | Notes |
|---|---|---|
| Products and brand | GET /api/v1/menu and GET /api/v1/shop-config with the publishable key, read uncached. | Grosze, PLN. Unavailable items omitted. Brand, hours, modules and ordering rules come from the panel. |
| Cart | { itemId, quantity, options? }[]. The on-screen total comes from the product list. | Options are chosen through options: [{ group, option }]. The client sends what, never how much. |
| Checkout | POST /api/v1/checkout through the /api/delieta/checkout shim, the outcome from GET /api/v1/checkout/result. | Delieta prices from the catalogue; a payment address comes back or, for cash, a trackingUrl. |
flowchart TD env["Env: origin, klucz publikowalny, sekret odswiezania"] --> rt["@delieta/shop-runtime"] rt --> menu["GET /menu i /shop-config bez cache"] menu --> cart["Koszyk: itemId, ilosc, opcje"] cart --> pipe["/api/delieta/checkout"] pipe --> checkout["POST /checkout, ceny liczy Delieta"] checkout --> result["GET /checkout/result i status"] secret["Klucz sekretny"] -.->|"nigdy w sklepie"| rt push["Delieta: podpisany POST /api/revalidate"] --> rt
The key is “publishable” only in the sense that it cannot read orders, drivers or customers. Even so, it stays on the server.
Two shims in the shop repository
A shop has exactly two files from us. Each is a single export; you do not edit them, and delieta-shop-assert fails when someone does.
// app/api/delieta/[...path]/route.ts
export { GET, POST } from "@delieta/shop-runtime/proxy";
// app/api/revalidate/route.ts
export { POST } from "@delieta/shop-runtime/revalidate";
The first is the /api/delieta/… proxy with an allowlist of paths: GET floor plan, reservation availability, checkout slots and order status by short code, POST checkout, reservations and inquiries. Everything else is refused. The guest's browser calls the proxy, and it adds the key on the server side. The second receives the signed refresh.
Refresh, heartbeat and guard
The runtime reads Delieta uncached. When the owner changes the menu, settings or floor plan, Delieta sends a signed POST /api/revalidate and the shop expires its tags (menu, shop-config, floor-plan). After next build, delieta-shop-assert checks three things: the shims are untouched, the app is not a static export, the publishable key did not leak into client code. It also sends the heartbeat with the runtime version. An app that is a static export has no server, so it does not meet contract v3.
Money and errors
Amounts are in whole grosze; format them only when displaying. Partner API errors are application/problem+json; identify them by code, for example unauthorized, scope_required, rate_limited, shop_closed, slot_unavailable. The full list of codes and every operation a shop uses are in Partner API.

