# Record an allowlisted Storefront analytics event Source: https://docs.craveup.com/api-reference/analytics/record-an-allowlisted-storefront-analytics-event /generated/storefront.openapi.json post /locations/{locationId}/analytics-events # Add an item to a cart Source: https://docs.craveup.com/api-reference/cart-items/add-an-item-to-a-cart /generated/storefront.openapi.json post /locations/{locationId}/carts/{cartId}/items # Remove an item from a cart Source: https://docs.craveup.com/api-reference/cart-items/remove-an-item-from-a-cart /generated/storefront.openapi.json delete /locations/{locationId}/carts/{cartId}/items/{itemId} # Update a cart item quantity Source: https://docs.craveup.com/api-reference/cart-items/update-a-cart-item-quantity /generated/storefront.openapi.json patch /locations/{locationId}/carts/{cartId}/items/{itemId} # Accept the current authoritative cart changes Source: https://docs.craveup.com/api-reference/carts/accept-the-current-authoritative-cart-changes /generated/storefront.openapi.json post /locations/{locationId}/carts/{cartId}/catalog-revalidation/accept # Claim a guest cart for the signed-in customer Source: https://docs.craveup.com/api-reference/carts/claim-a-guest-cart-for-the-signed-in-customer /generated/storefront.openapi.json post /locations/{locationId}/carts/{cartId}/claim # Expire a cart Source: https://docs.craveup.com/api-reference/carts/expire-a-cart /generated/storefront.openapi.json delete /locations/{locationId}/carts/{cartId} # Get an authorized cart Source: https://docs.craveup.com/api-reference/carts/get-an-authorized-cart /generated/storefront.openapi.json get /locations/{locationId}/carts/{cartId} # List cart recommendations Source: https://docs.craveup.com/api-reference/carts/list-cart-recommendations /generated/storefront.openapi.json get /locations/{locationId}/carts/{cartId}/products # Set cart delivery address Source: https://docs.craveup.com/api-reference/carts/set-cart-delivery-address /generated/storefront.openapi.json put /locations/{locationId}/carts/{cartId}/set-delivery # Set room service details Source: https://docs.craveup.com/api-reference/carts/set-room-service-details /generated/storefront.openapi.json put /locations/{locationId}/carts/{cartId}/set-room # Set table service details Source: https://docs.craveup.com/api-reference/carts/set-table-service-details /generated/storefront.openapi.json put /locations/{locationId}/carts/{cartId}/set-table # Start a cart at another merchant location Source: https://docs.craveup.com/api-reference/carts/start-a-cart-at-another-merchant-location /generated/storefront.openapi.json post /locations/{locationId}/carts/{cartId}/location-switches # Update cart fields Source: https://docs.craveup.com/api-reference/carts/update-cart-fields /generated/storefront.openapi.json patch /locations/{locationId}/carts/{cartId} # Update cart gratuity Source: https://docs.craveup.com/api-reference/carts/update-cart-gratuity /generated/storefront.openapi.json put /locations/{locationId}/carts/{cartId}/gratuity # Update cart order time Source: https://docs.craveup.com/api-reference/carts/update-cart-order-time /generated/storefront.openapi.json put /locations/{locationId}/carts/{cartId}/update-order-time # Update the DoorDash courier tip Source: https://docs.craveup.com/api-reference/carts/update-the-doordash-courier-tip /generated/storefront.openapi.json put /locations/{locationId}/carts/{cartId}/courier-tip # Confirm an authoritative provider payment Source: https://docs.craveup.com/api-reference/checkout/confirm-an-authoritative-provider-payment /generated/storefront.openapi.json post /locations/{locationId}/carts/{cartId}/payments # Create a one-time checkout handoff Source: https://docs.craveup.com/api-reference/checkout/create-a-one-time-checkout-handoff /generated/storefront.openapi.json post /locations/{locationId}/carts/{cartId}/checkout-handoffs # Create or recover the authoritative cart payment session Source: https://docs.craveup.com/api-reference/checkout/create-or-recover-the-authoritative-cart-payment-session /generated/storefront.openapi.json post /locations/{locationId}/carts/{cartId}/payment-session # Exchange a one-time checkout handoff Source: https://docs.craveup.com/api-reference/checkout/exchange-a-one-time-checkout-handoff /generated/storefront.openapi.json post /locations/{locationId}/carts/{cartId}/checkout-handoffs/exchange # Poll checkout order state Source: https://docs.craveup.com/api-reference/checkout/poll-checkout-order-state /generated/storefront.openapi.json get /locations/{locationId}/carts/{cartId}/order-result # Submit a rating after successful payment Source: https://docs.craveup.com/api-reference/checkout/submit-a-rating-after-successful-payment /generated/storefront.openapi.json post /locations/{locationId}/carts/{cartId}/rating # Validate checkout and update customer contact details Source: https://docs.craveup.com/api-reference/checkout/validate-checkout-and-update-customer-contact-details /generated/storefront.openapi.json put /locations/{locationId}/carts/{cartId}/validate-and-update # Revoke the customer session Source: https://docs.craveup.com/api-reference/customer-authentication/revoke-the-customer-session /generated/storefront.openapi.json delete /customer/logout # Start merchant-bound customer login Source: https://docs.craveup.com/api-reference/customer-authentication/start-merchant-bound-customer-login /generated/storefront.openapi.json post /customer/auth/login # Verify customer OTP Source: https://docs.craveup.com/api-reference/customer-authentication/verify-customer-otp /generated/storefront.openapi.json post /customer/auth/verify-otp # Buy and activate a Square digital gift card Source: https://docs.craveup.com/api-reference/customers/buy-and-activate-a-square-digital-gift-card /generated/storefront.openapi.json post /customer/locations/{locationId}/gift-cards/sales # Confirm a customer phone verification code Source: https://docs.craveup.com/api-reference/customers/confirm-a-customer-phone-verification-code /generated/storefront.openapi.json post /customer/phone-verification/confirmation # Create a customer address Source: https://docs.craveup.com/api-reference/customers/create-a-customer-address /generated/storefront.openapi.json post /customer/addresses # Delete a customer address Source: https://docs.craveup.com/api-reference/customers/delete-a-customer-address /generated/storefront.openapi.json delete /customer/addresses/{addressId} # Delete a saved payment method Source: https://docs.craveup.com/api-reference/customers/delete-a-saved-payment-method /generated/storefront.openapi.json delete /customer/locations/{locationId}/saved-payments/{paymentId} # Get Square digital gift-card sale settings Source: https://docs.craveup.com/api-reference/customers/get-square-digital-gift-card-sale-settings /generated/storefront.openapi.json get /customer/locations/{locationId}/gift-cards/sale-session # Get the signed-in customer profile Source: https://docs.craveup.com/api-reference/customers/get-the-signed-in-customer-profile /generated/storefront.openapi.json get /customer # List customer addresses Source: https://docs.craveup.com/api-reference/customers/list-customer-addresses /generated/storefront.openapi.json get /customer/addresses # List saved payment methods Source: https://docs.craveup.com/api-reference/customers/list-saved-payment-methods /generated/storefront.openapi.json get /customer/locations/{locationId}/saved-payments # Send a customer phone verification code Source: https://docs.craveup.com/api-reference/customers/send-a-customer-phone-verification-code /generated/storefront.openapi.json post /customer/phone-verification # Update a customer address Source: https://docs.craveup.com/api-reference/customers/update-a-customer-address /generated/storefront.openapi.json patch /customer/addresses/{addressId} # Apply a discount code Source: https://docs.craveup.com/api-reference/discounts/apply-a-discount-code /generated/storefront.openapi.json post /locations/{locationId}/carts/{cartId}/discount # Remove the applied discount Source: https://docs.craveup.com/api-reference/discounts/remove-the-applied-discount /generated/storefront.openapi.json delete /locations/{locationId}/carts/{cartId}/discount # Calculate distance to a location Source: https://docs.craveup.com/api-reference/locations/calculate-distance-to-a-location /generated/storefront.openapi.json post /locations/{locationId}/distance # Get a published location Source: https://docs.craveup.com/api-reference/locations/get-a-published-location /generated/storefront.openapi.json get /locations/{locationSlugOrId} # Get available order times Source: https://docs.craveup.com/api-reference/locations/get-available-order-times /generated/storefront.openapi.json get /locations/{locationId}/time-intervals # Get gratuity configuration Source: https://docs.craveup.com/api-reference/locations/get-gratuity-configuration /generated/storefront.openapi.json get /locations/{locationId}/gratuity # Redirect to the hosted location storefront Source: https://docs.craveup.com/api-reference/locations/redirect-to-the-hosted-location-storefront /generated/storefront.openapi.json get /locations/{locationId}/redirect # Get customer loyalty balances and ledger Source: https://docs.craveup.com/api-reference/loyalty/get-customer-loyalty-balances-and-ledger /generated/storefront.openapi.json get /customer/locations/{locationId}/loyalty/ledger # Get loyalty earning and reward options Source: https://docs.craveup.com/api-reference/loyalty/get-loyalty-earning-and-reward-options /generated/storefront.openapi.json get /locations/{locationId}/carts/{cartId}/loyalty/quote # Join the checkout's authoritative loyalty program Source: https://docs.craveup.com/api-reference/loyalty/join-the-checkouts-authoritative-loyalty-program /generated/storefront.openapi.json post /locations/{locationId}/carts/{cartId}/loyalty/enrollment # List customer loyalty claims Source: https://docs.craveup.com/api-reference/loyalty/list-customer-loyalty-claims /generated/storefront.openapi.json get /customer/loyalty/claims # Remove the applied loyalty reward Source: https://docs.craveup.com/api-reference/loyalty/remove-the-applied-loyalty-reward /generated/storefront.openapi.json delete /locations/{locationId}/carts/{cartId}/loyalty/redeem # Reserve and apply a loyalty reward Source: https://docs.craveup.com/api-reference/loyalty/reserve-and-apply-a-loyalty-reward /generated/storefront.openapi.json post /locations/{locationId}/carts/{cartId}/loyalty/redeem # Submit a loyalty claim Source: https://docs.craveup.com/api-reference/loyalty/submit-a-loyalty-claim /generated/storefront.openapi.json post /customer/loyalty/claims # List the published menu bundle Source: https://docs.craveup.com/api-reference/menus/list-the-published-menu-bundle /generated/storefront.openapi.json get /locations/{locationId}/menus # Get a published merchant and its locations Source: https://docs.craveup.com/api-reference/merchants/get-a-published-merchant-and-its-locations /generated/storefront.openapi.json get /merchant/{merchantSlug} # Check ordering availability without creating a cart Source: https://docs.craveup.com/api-reference/ordering-sessions/check-ordering-availability-without-creating-a-cart /generated/storefront.openapi.json get /locations/{locationId}/ordering-readiness # Create or resume an ordering session Source: https://docs.craveup.com/api-reference/ordering-sessions/create-or-resume-an-ordering-session /generated/storefront.openapi.json post /locations/{locationId}/ordering-sessions # Get an authorized public receipt Source: https://docs.craveup.com/api-reference/orders/get-an-authorized-public-receipt /generated/storefront.openapi.json get /receipts/{receiptId} # Get customer order details Source: https://docs.craveup.com/api-reference/orders/get-customer-order-details /generated/storefront.openapi.json get /customer/orders/{orderId} # List customer orders Source: https://docs.craveup.com/api-reference/orders/list-customer-orders /generated/storefront.openapi.json get /customer/orders # API Overview Source: https://docs.craveup.com/api-reference/overview/api-overview Use the direct public Storefront API with capabilities, customer JWTs, and optimistic concurrency. ## Base URLs ```text theme={null} Production: https://api.craveup.com/api/v1/storefront Sandbox: https://dev-api-43233223.craveup.com/api/v1/storefront ``` ## Authorization The public API does not use a browser API key. | Resource | Authorization | | - | - | | Published merchant, location, menu, product, order-time, gratuity | Anonymous | | Guest cart | Purpose-limited `X-Cart-Token` capability | | Customer profile, orders, addresses, saved payments, loyalty history | Merchant-bound bearer JWT | | Receipt | Customer JWT or purpose-limited `X-Receipt-Token` capability | Private admin and integration credentials belong only in trusted server or CLI environments. ## Start an ordering session ```bash theme={null} curl -X POST "https://api.craveup.com/api/v1/storefront/locations/LOCATION_ID/ordering-sessions" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: session_01JEXAMPLE" \ -d '{"fulfillmentMethod":"takeout"}' ``` The response contains `cart` and, for a newly created guest cart, `cartAccessToken`. Save the token in tab-scoped storage. The response `ETag` contains the cart revision. ## Mutate the cart ```bash theme={null} curl -X POST "https://api.craveup.com/api/v1/storefront/locations/LOCATION_ID/carts/CART_ID/items" \ -H "Content-Type: application/json" \ -H "X-Cart-Token: CART_CAPABILITY" \ -H 'If-Match: "cart-0"' \ -H "Idempotency-Key: item_01JEXAMPLE" \ -d '{"productId":"PRODUCT_ID","quantity":1,"selections":[],"itemUnavailableAction":"remove_item"}' ``` Use the returned `ETag` for the next mutation. A stale revision returns `409 CART_CONFLICT`; reload current cart state before offering a retry. ## Checkout ```text theme={null} POST /locations/{locationId}/carts/{cartId}/payment-session POST /locations/{locationId}/carts/{cartId}/payments GET /locations/{locationId}/carts/{cartId}/order-result POST /locations/{locationId}/carts/{cartId}/rating GET /receipts/{receiptId} ``` The order result is one of `payment_pending`, `order_pending`, `completed`, or `failed`. Only `completed` is success. ## Customer authentication ```text theme={null} POST /customer/auth/login POST /customer/auth/verify-otp GET /customer GET /customer/orders GET /customer/addresses GET /customer/saved-payments GET /customer/locations/{locationId}/loyalty/ledger ``` Login initiation includes only `merchantSlug` and the email/phone identifier. OTP verification repeats both values and may include customer profile fields after identity proof, binding the resulting session to one merchant. ## Error envelope ```json theme={null} { "code": "CART_CONFLICT", "message": "The cart changed. Refresh it before retrying.", "requestId": "req_123", "details": {} } ``` Common statuses are `400` validation, `401` invalid capability/session, `404` hidden or missing resource, `409` state/revision conflict, `429` rate limit, `503` dependency unavailable, and `500` internal error. ## SDK ```ts theme={null} const crave = createStorefrontClient({ baseUrl: process.env.NEXT_PUBLIC_CRAVEUP_API_URL!, sessionStore, getAuthToken, }); ``` Prefer the SDK for TypeScript storefronts because it owns capability headers, revisions, idempotency defaults, timeouts, and typed errors. # Core Concepts Source: https://docs.craveup.com/api-reference/overview/core-concepts Understand the data model behind merchants, locations, menus, carts, and orders. The Storefront API is built around five entities: **merchants**, **locations**, **menus**, **carts**, and **orders**. This page explains how they relate to each other and what data each one carries. ## Merchants A merchant represents a restaurant brand. Each merchant has a name, logo, and one or more locations. ```ts theme={null} const merchant = await storefront.merchant.getBySlug('downtown-pizza'); merchant.id; // "5b2e7f71-62cf-4bd5-b84e-df17f1f18927" merchant.name; // "Downtown Pizza Co." merchant.locations; // MerchantLocation[] ``` Use `merchant.getBySlug()` to resolve a brand slug into its list of locations. Each location in the response includes an `id`, display name, address, logo, and a `methodsStatus` object indicating which fulfillment methods are enabled. ## Locations A location is a single restaurant site. All storefront operations — menus, carts, payments — are scoped to a location. ```ts theme={null} const location = await storefront.locations.getById('f8af3da4-f70e-4521-a04f-716c0c04fb77'); location.restaurantDisplayName; // "Downtown Pizza — Market St" location.addressString; // "123 Market St, San Francisco, CA 94105" location.addressData; // { street, city, state, zipCode, country, lat, lng } ``` ### Location identifiers You can reference a location by either its UUID or slug. Both work in storefront location endpoints: ``` /api/v1/storefront/locations/f8af3da4-f70e-4521-a04f-716c0c04fb77/... /api/v1/storefront/locations/downtown-pizza/... ``` ### Fulfillment methods Each location enables a subset of these fulfillment methods: | Method | Value | Description | | - | - | - | | Pickup | `takeout` | Customer picks up at the counter | | Delivery | `delivery` | Delivered to customer's address | | Table-side | `table_side` | Served to a table number | | Room service | `room_service` | Delivered to a hotel room | Check `methodsStatus` on the merchant location response to determine which options to show: ```ts theme={null} const loc = merchant.locations[0]; loc.methodsStatus.pickup; // true loc.methodsStatus.delivery; // true loc.methodsStatus.table; // false loc.methodsStatus.roomService; // false ``` ### Order times Locations define when they accept orders. Fetch the available time slots before showing a schedule picker: ```ts theme={null} const times = await storefront.locations.getOrderTimes('f8af3da4-f70e-4521-a04f-716c0c04fb77'); times.scheduleAllowed; // true — scheduled orders accepted times.requireScheduledOrders; // false — ASAP is available times.orderDays; // RegularOrderDay[] or SpecialOrderDay[] ``` ## Menus, categories, and products Each location has one or more **menus**. A menu contains **categories**, and each category links to **products**. The active menu depends on the time of day. ``` Merchant → Location → Menu → Category → Product → Modifier ``` ### Products A product represents a single orderable item: | Field | Type | Description | | - | - | - | | `id` | `string` | Unique product ID | | `name` | `string` | Display name | | `description` | `string` | Short description | | `price` | `string` | Base price as a decimal string (e.g., `"12.99"`) | | `displayPrice` | `string` | Formatted price with currency symbol | | `images` | `string[]` | Product image URLs | | `modifiers` | `Modifier[]` | Available customizations | | `availability` | `string` | Current availability status | | `nutrition` | `object \| undefined` | Calorie count, dietary preferences, ingredients | ### Modifiers Modifiers let customers customize a product (e.g., size, toppings, extras). Each modifier group has selection rules: ```ts theme={null} interface Modifier { id: string; name: string; // "Choose your size" rule: { min: number; // 1 — must select at least one max: number; // 1 — can select at most one }; items: ModifierItem[]; // [{ id, name, price, maxQuantity }] } ``` Modifier items can have nested child groups for multi-level customization (e.g., "Choose your protein" > "Choose your preparation"). ## Carts A cart holds the customer's selections and computes all pricing automatically. You create a cart by starting an **ordering session**: ```ts theme={null} const session = await storefront.orderingSessions.start('loc_123', { marketplaceId: 'web', fulfillmentMethod: 'takeout', }); const cartId = session.cart.id; ``` `marketplaceId` is optional source attribution, such as `"web"` or `"mobile"`; it is not a location identifier. ### Cart lifecycle | Status | Meaning | | - | - | | `OPEN` | Active — items can be added, removed, or updated | | `LOCKED` | Payment is processing — no modifications allowed | | `COMPLETED` | Payment confirmed — order sent to the restaurant | | `EXPIRED` | Capability is no longer valid; start a new cart | ### Automatic pricing Every time you modify the cart, the API recalculates all totals: | Field | Description | | - | - | | `subTotal` | Sum of item prices before tax and fees | | `taxTotal` | Tax based on the location's tax rates | | `serviceFeeTotal` | Platform service fee | | `fulfillmentMethodFeeTotal` | Delivery or fulfillment fee | | `waiterTipTotal` | Customer gratuity | | `orderTotalWithServiceFee` | Final total the customer pays | Price fields are decimal strings. Format them using the cart currency and the customer's locale. ### Cart items Each item in the cart tracks its product, quantity, modifier selections, special instructions, and computed totals: ```ts theme={null} const cart = await storefront.cart.get('loc_123', cartId); cart.items.forEach((item) => { item.name; // "Margherita Pizza" item.quantity; // 2 item.total; // "25.98" item.selections; // CartModifierGroup[] — chosen modifiers item.specialInstructions; // "Extra crispy" }); ``` ### Fulfillment configuration Before checkout, set the fulfillment method on the cart. Each method requires different data: | Method | API call | Required data | | - | - | - | | Pickup | `cart.update(...)` | `{ fulfillmentMethod: 'takeout' }` | | Delivery | `cart.setDelivery(...)` | Full address with `lat`/`lng` | | Table-side | `cart.setTable(...)` | Numeric table string such as `"12"` | | Room service | `cart.setRoom(...)` | Room number and last name | ## Payments Crave processes payments through Stripe Connect. You create a `PaymentIntent` via the API and confirm it on the client with Stripe.js: ```ts theme={null} const { clientSecret } = await storefront.checkout.createPaymentIntent('loc_123', cartId); ``` | Field | Description | | - | - | | `clientSecret` | Pass to `stripe.confirmPayment()` on the client | ## Orders Orders are created automatically when a payment succeeds — you do not create them via the API. After payment, the order appears in the restaurant's merchant dashboard for fulfillment. Poll `storefront.checkout.getOrderResult()` after payment. It distinguishes `payment_pending`, `order_pending`, `completed`, and `failed`. Signed-in customers can list and read only their own orders through `storefront.customer.orders`. ## Analytics events Track key funnel events to measure storefront performance: ```ts theme={null} await storefront.analyticsEvents.track('loc_123', { cartId, eventType: 'CHECKOUT_VIEW', // 'SCAN' | 'CART_VIEW' | 'CHECKOUT_VIEW' }); ``` Every public analytics event requires authorization for its cart. `ORDER_PLACED` is recorded by the API during authoritative order creation and is not accepted from clients. ## Currencies and countries The API supports these currencies and countries: | Currency | Code | Countries | | - | - | - | | US Dollar | `usd` | US | | British Pound | `gbp` | GB | | UAE Dirham | `aed` | AE | | Australian Dollar | `aud` | AU | Currency is set at the merchant level and applies to all locations under that merchant. ## Next steps Fetch menus, categories, and products for a location. Add items, apply modifiers, and manage the cart lifecycle. Collect customer details, set order time, and process payment. Configure delivery, table-side, and room service. # Get a published product Source: https://docs.craveup.com/api-reference/products/get-a-published-product /generated/storefront.openapi.json get /locations/{locationId}/products/{productId} # Changelog Source: https://docs.craveup.com/changelog Track improvements to the Crave storefront documentation and OpenAPI reference. ### New * **Dart and Flutter SDK** – Added first-class installation, environment, secure-session, readiness, and typed-error guidance for `crave_storefront_sdk`. * **Flutter quickstart** – Added an end-to-end path for configuring a deployment, checking ordering readiness, loading a menu, and starting an order. ### Improved * **TypeScript SDK** – Updated current installation guidance to the exact `@craveup/storefront-sdk@2.0.1` patch while preserving the durable 1.x migration history. * **Environment safety** – Clarified that sandbox and production use separate deployments, variables, credentials, and session namespaces rather than an in-app production toggle. ### Fixed * **OpenAPI reproducibility** – Pointed Mintlify at the reviewed generated Storefront OpenAPI artifact committed with the documentation. * **Developer portal parity** – Replaced the retired browser-key example and stale documentation domain with the real SDK client and canonical docs links. ### New * **Return URL support** – Added configurable return URLs across storefront API, admin API, and the SDK so partners can guide guests to custom destinations after checkout. * **Cart metadata hooks** – Extended cart payloads so loyalty and analytics tools can append metadata without breaking totals. ### Improved * **Mintlify parity** – Regenerated OpenAPI artifacts directly from Express routes and rewrote MDX guides so docs mirror production behaviour. * **SDK alignment** – Synced storefront SDK method names and request signatures with the latest Swagger definitions. ### Fixed * **Spec drift** – Resolved schema mismatches between Swagger and Mintlify by pointing both builds at the same generated artifact. * **Checkout redirects** – Patched edge cases where return URLs were dropped for certain ordering sessions. ### New * **Orders hub** – Introduced a token-protected orders portal with session creation, revocation, and analytics wiring for operations teams. * **Embeddable pricing** – Shipped a drop-in pricing table, enterprise entity management, and sales-friendly landing pages. * **Responsive UI primitives** – Published responsive dialog and sheet components in the shared UI package for both dashboard and storefront use. ### Improved * **Tailwind v4 + shadcn** – Migrated every app to the Tailwind v4 pipeline, standardized tokens, and removed duplicated component code. * **Onboarding guidance** – Added unsaved-change bars, fulfilment method milestones, and confetti banners so merchants know when setup is complete. ### Fixed * **Checkout ergonomics** – Reworked the cart sidebar, order time picker, and thank-you flow to eliminate crashy states. * **Error consistency** – Normalized API error responses and logging across admin, storefront, and order services. ### New * **Location wizard** – Added account-setup steps with map pickers, contact info endpoints, and multi-location management inside the dashboard. * **Order manager API** – Expanded admin endpoints for active, scheduled, and historical orders with dashboards and detail drawers. * **Crave Admin shell** – Launched the standalone site for enterprise management alongside embeddable pricing and enterprise profiles. ### Improved * **Discount workflow** – Polished validation, loading states, and edit/delete flows for promotions. * **Ordering session flow** – Hardened session APIs, menu bundles, and redirect handling so storefronts stay in sync. ### Fixed * **Environment + slug bugs** – Resolved slug mismatches, missing env vars, and redirect loops introduced during the restructure. * **Checkout error states** – Tightened error messaging for bundle fetches, checkout validation, and order receipts. ### New * **Store settings redesign** – Rolled out cohesive layouts for store info, fulfilment, holiday hours, tips, and notifications with mobile-friendly navigation. * **Developer workspace** – Introduced a Resend-inspired developer page with code previews, dark mode, and API contact flows. * **Onboarding wizard** – Delivered a multi-step welcome flow, sample dashboards, and progress trackers for new organizations. ### Improved * **Multi-location support** – Upgraded navigation, location selectors, and analytics to handle complex brands. * **Subscription management** – Updated pricing tiers, added cancellation banners, and clarified plan benefits in product copy. ### Fixed * **Stripe + auth flows** – Hardened payment intent handling, sign-up flows, and logout paths across admin and storefront apps. * **Dark mode polish** – Cleaned up contrast issues across headers, buttons, and schedule components. ### New * **Menu templates & defaults** – Seeded menu, category, and modifier forms with templates, default schedules, and currency hints to shorten setup. * **Stytch metadata sync** – Synced organization metadata, subscription state, and location identifiers directly with Stytch. ### Improved * **Error boundary middleware** – Wrapped every admin route with a shared `withErrorHandler` helper to centralize logging and responses. * **Store settings** – Upgraded tipping logic, address flows, and onboarding redirects to guide merchants to completion. ### Fixed * **Validation gaps** – Smoothed subscription persistence, rehydrated location IDs, and tightened toast feedback during onboarding. * **Access controls** – Restricted dashboard-only routes and enforced metadata checks before exposing sensitive endpoints. ### New * **Comprehensive Swagger docs** – Authored storefront, admin, menu, analytics, discounts, and team endpoints with generated JSON sources. * **Shared packages** – Promoted env, db, logger, and utils into reusable workspace modules with type-safe exports. ### Improved * **Next.js 15 + modules** – Upgraded partner and storefront apps to Next.js 15, reorganized imports, and aligned modules with ESM standards. * **Repository hygiene** – Removed legacy guest ordering code, normalized naming, and cleaned duplicate utilities. ### Fixed * **Stripe + webhook logging** – Standardized logging hooks and resolved Stripe route regressions uncovered during doc generation. * **Storybook coverage** – Patched component loaders and accessibility props flagged while documenting new flows. ### New * **Modular apps** – Split the monorepo into dedicated Express admin/storefront APIs and Next.js ordering/partner surfaces using the latest Turbo scaffolding. * **Admin console prototype** – Bootstrapped the Crave Admin app with data tables, navigation, and shared UI packages. * **UI kit publishing** – Added shadcn-ready components plus shared ESLint and TypeScript configs for every project. ### Improved * **Type safety** – Centralized types, resolved module resolution pitfalls, and aligned packages on strict TypeScript settings. * **Build tooling** – Added pnpm/Turbo scripts to install dependencies, lint, and test reliably across workspaces. ### Fixed * **Legacy imports** – Cleared unused hooks, duplicate components, and inconsistent paths carried over from earlier repos. * **Order endpoints** – Stabilized active/history/scheduled order routes ahead of the new admin console. ### New * **Customer card metadata** – Aligned stored card records with customer IDs so repeat guests see the right payment options. * **Form primitives** – Introduced reusable checkbox and wrapper components ahead of the full shadcn migration. ### Improved * **Checkout layout** – Tightened page wrappers, skeleton states, and CTA spacing to reduce friction on mobile. * **Cart experience** – Smoothed quantity controls and tip defaults while preparing the cart sidebar revamp. ### Fixed * **Payment glitches** – Resolved regressions around card selection, validation, and optimistic UI while orders are syncing. * **Styling drift** – Squashed stray pointer-events, HTML entities, and prop casing issues that surfaced after the holiday builds. ### New * **Transactional messaging** – Wired SendGrid email and Twilio SMS sends so guests and teams receive real-time order confirmations. * **Store preview mockup** – Embedded an iPhone live preview within store settings so teams see branding updates instantly. * **Menu manager shortcuts** – Enabled expanding categories by default and adding items/categories directly from the overview screen. ### Improved * **Orders workspace** – Added filtered views, range calendars, and status chips to streamline fulfillment workflows. * **Dashboard navigation** – Introduced dynamic store-settings navigation, refreshed onboarding copy, and persisted trial banners. ### Fixed * **Checkout polish** – Tightened tip defaults, modifier validation, payment button behaviour, and cart totals after shadcn component migration. * **Menu reliability** – Resolved nested button hydration errors, null checks, and pointer-event regressions spotted during QA. ### New * **Order operations workspace** – Added filtered order lists, export tooling, and richer status insights so operators can reconcile sales without leaving the dashboard. * **Team invitations & roles** – Introduced invite flows with pending/expired states, location scoping, and inline status badges for a clearer view of collaborator access. * **API key lifecycle controls** – Surfaced key creation and deletion inside the developer settings so teams can rotate credentials without engineering help. ### Improved * **Onboarding completion flow** – Refined step guidance, loaders, and success messaging to celebrate completion and nudge merchants toward key setup actions. * **Location metadata** – Polished slugs, SEO metadata, and storefront links for cleaner sharing and cross-linking across partner surfaces. ### Fixed * **Authentication cookies** – Hardened Stytch cookie handling to resolve cross-domain login edge cases and flaky session persistence. * **Address capture** – Patched location onboarding to guard against partially saved address steps and duplicate business details. ### New * **Developer center** – Published an in-app developers page with API key generation, waiter tip configuration, and quick links back to docs. * **Discount builder** – Delivered a guided form for creating percentage and amount-off promos with usage limits, schedules, and summary previews. * **Store settings depth** – Added waiter-tip policies, tax rate cards, and granular fulfillment scheduling to cover operational edge cases. ### Improved * **Onboarding progression** – Synced the universal task checklist with confetti states, loader components, and error handling for a smoother start. * **Performance polish** – Introduced shimmer loaders, refined layout spacing, and reorganized folders to keep the dashboard responsive at scale. ### Fixed * **Endpoint reliability** – Tightened API contracts, types, and environment configuration so admin traffic stops tripping validation errors in production. * **Discount validation** – Addressed edge cases around duplicate codes, date ranges, and negative values before launch. ### New * **Guided onboarding** – Rolled out progress badges, celebratory confetti, and contextual upsells so merchants know exactly what unlocks go-live. * **Menu studio** – Enabled create/edit/delete flows for menus, categories, products, and modifier groups directly inside the dashboard. * **Team management** – Connected invite, delete, and list endpoints so admins can manage collaborators without support tickets. ### Improved * **Fulfillment settings** – Added tabbed forms, schedule pickers, and defaults across delivery, pickup, and room service to cut configuration time. * **Store hours tooling** – Synced location hours with validation, auto-fill defaults, and address autocomplete to reduce onboarding friction. ### Fixed * **Authentication & routing** – Resolved stale token handling, ensured location fetches respect organization context, and removed unused codepaths. ### New * **Stripe subscriptions** – Wired subscription plans, free trial entry points, and Connect onboarding so teams can self-serve upgrades. * **Discount workflows** – Completed the promotion builder with date/time scheduling, code generation, and React Hook Form summaries. ### Improved * **Start free trial UX** – Refined copy, country defaults, and toast feedback to boost conversion through the billing funnel. * **Connect experiences** – Iterated on Stripe Connect screens and reusable dropdowns for a consistent payments setup journey. ### Fixed * **Location data hydration** – Hardened location loaders and hook defaults so dashboards stop flashing empty states on refresh. * **Package hygiene** – Raised dependency baselines to clear high-severity security advisories. ### New * **Stytch SSO** – Replaced bespoke auth with Stytch-powered signup, login, and organization switching, unlocking OAuth and email-first flows. * **Subscription plans hub** – Released subscription management with plan selection, add-ons, and plan enforcement inside the dashboard. * **Analytics foundation** – Delivered merchant analytics endpoints, export tooling, and dashboard charts for real-time performance tracking. ### Improved * **Scheduling UI** – Rebuilt fulfillment scheduling with reusable components, validation, and error prompts that mirror the live storefront logic. * **Order operations** – Added order action toasts, filtered lists, and skeleton states to help managers triage tickets faster. ### Fixed * **Security posture** – Cleared outstanding vulnerability alerts by bumping packages and removing unused dependencies. * **Integration wiring** – Consolidated scattered fetchers and unused endpoints to keep telemetry dashboards accurate. ### New * **OTP flows** – Added resend timers, auto-submit, and inline error toasts for a seamless verification experience on checkout. * **Saved payment controls** – Enabled guests to remove saved cards and clear cart state once an order is complete. ### Improved * **Loading states** – Rolled out skeletons, loaders, and disabled states across homepage, cart, and checkout to improve perceived performance. * **Error messaging** – Surfaced precise validation feedback for modifiers, order time selectors, and fulfilment switching. ### Fixed * **UI alignment** – Patched navbar spacing, modal heights, and image ratios so the storefront feels polished on every device. * **Cart consistency** – Squashed duplicate items and race conditions introduced by rapid add/remove actions. ### New * **Schedule delivery skeletons** – Added skeleton states, map previews, and order-time selectors that respect location availability. * **Complementary cart** – Recommended add-on items with dedicated sliders and API-backed data. * **Room service fulfillment** – Brought table-side fulfilment parity with delivery and pickup, including modal flows and validations. ### Improved * **Cart logic** – Refactored prepare-cart flows, ensured location IDs propagate, and tightened revalidation. * **Fulfillment designs** – Unified styling and responsiveness for fulfilment tabs, modals, and empty states. ### Fixed * **Add to cart edge cases** – Addressed rounding, skeleton flickers, and missing imagery in popular-product sliders. * **Type hygiene** – Cleaned up lingering TypeScript errors so builds stay green. ### New * **Menu editor** – Added data tables, inline dialogs, and bulk actions for menus, categories, products, and modifiers. * **Operational pages** – Launched dedicated views for discounts, statements, teams, integrations, and account settings. * **Payments setup** – Integrated Stripe Setup Intents, OTP flows, and bank account onboarding for merchants. ### Improved * **Component library** – Expanded Tremor-based components, wrappers, and responsive layouts used across onboarding. * **Cart & checkout UX** – Finished checkout flows with skeletons, promo code modals, and cart formatting parity. ### Fixed * **Product dialogs** – Resolved duplicate renders, scroll traps, and hardcoded imagery that crept into the early rebuild. * **Type coverage** – Swept through new packages to align on shared types and eliminate implicit any usage. ### New * **Redux Toolkit** – Centralized storefront state, unlocking persisted carts, fulfilment toggles, and shared selectors. * **Fulfilment APIs** – Completed place-order, delivery timing, and gratuity endpoints with client integrations. * **Location info surface** – Exposed location details, tips, and announcements directly in the partner UI. ### Improved * **Navigation scaffolding** – Tightened sticky tab borders, section scrolling, and category selection experiences. * **Design tokens** – Extended Tailwind palette, typography, and shared buttons to keep storefront branding consistent. ### Fixed * **Cart formatting** – Normalized currency output and ensured duplicate add-to-cart mutations merge correctly. * **Legacy packages** – Removed unused dependencies before shipping to partners. ### New * **Data fetcher utilities** – Added SWR fetchers, shared constants, and API connectors for homepage and menu data. * **Homepage integrations** – Wired the Next.js storefront to live product, timing, and location endpoints. ### Improved * **Design system** – Continued Tailwind UI build-out with iconography, modals, and slider polish. * **Notifications** – Adopted react-toast notifications for system feedback. ### Fixed * **Code hygiene** – Deleted legacy scaffolding and unused exports left over from the first prototype. * **API casing** – Corrected header names and auth keys to match production conventions. ### New * **API key management** – Implemented key generation, rotation, and authorization middleware for partner integrations. * **Order manager endpoints** – Delivered status, fulfilment, and export routes for merchants to drive operations from the API. ### Improved * **Location access** – Linked API keys to location scoping and added guardrails for accessing multi-location menus. * **Documentation tooling** – Updated error naming, response shapes, and OpenAPI alignment across new endpoints. ### Fixed * **Endpoint inconsistencies** – Normalized casing (e.g., `X-API-Key`) and fixed authentication leaks discovered during QA. ### New * **Onboarding wizard** – Launched multi-step onboarding with business, tax, schedule, and payments configuration. * **Customer auth** – Introduced OTP verification, login/logout flows, and customer session middleware. * **Merchant subscription** – Added plan subscription, plan cancellation guardrails, and Stripe setup intents. ### Improved * **Storybook coverage** – Brought all major dashboard and storefront components into Storybook for team-wide reviews. * **UX consistency** – Harmonized typography, spacing, and color tokens across shared packages. ### Fixed * **Validation gaps** – Patched schema validation for forms, ensuring bad inputs are caught client and server side. * **Session handling** – Closed logout edge cases and stale cookie states in the customer APIs. ### New * **Atoms & molecules** – Shipped buttons, tabs, list tiles, checkout cards, toggles, modifiers, and more as reusable components. * **Storybook integration** – Documented the new component library with stories for every state. ### Improved * **Design tokens** – Expanded custom colors, font sizes, and line heights to match brand guidelines. * **Input ergonomics** – Refined form components, radio buttons, and selectors based on early feedback. ### Fixed * **Naming consistency** – Renamed components and files to align with PascalCase/Kebab-case conventions across the monorepo. * **Lint noise** – Cleaned up duplicate exports and lint warnings caused by rapid scaffolding. ### New * **Next.js partner app** – Bootstrapped the partner storefront with routing, layout primitives, and base styling. * **UI primitives** – Added dialog, button, store cover, and item card components to accelerate vertical slice builds. ### Improved * **Font & theming** – Applied the Crave brand typography stack and color palette across the new app. ### Fixed * **Component duplication** – De-duped early experiments while consolidating on Tremor and internal UI kits. ### New * **Cart lifecycle** – Implemented create, update, remove, and delete cart endpoints with quantity validation. * **Fulfilment prep** – Added cart item routes, gratuity calculations, and groundwork for order placement. ### Improved * **Rate limiting** – Hardened Express middleware to protect storefront APIs from abusive traffic. * **Code style** – Adopted Prettier and repository-wide formatting for the service codebase. ### Fixed * **Naming & structure** – Normalized file names and tightened up utilities to keep endpoints discoverable. ### New * **Express storefront** – Shipped the initial Express service with database connectivity, store info, and menu/category endpoints. * **Time utilities** – Added shared utilities for operating hours, laying the groundwork for scheduling logic. ### Improved * **Database wiring** – Iterated on DB connections and data-reading layers to stabilize the new stack. ### Fixed * **Deployment scaffolding** – Cleaned up early config so the v2 API could run locally and in staging. ### New * **White-label iOS pipeline** – Enabled merchants to generate branded mobile apps straight from the dashboard with live previews. * **Meta Pixel integration** – Brought first-party attribution to both the dashboard and guest storefront for unified campaign tracking. * **Subscription safeguards** – Automated Stripe billing with past-due cancellations, trial countdowns, and customer-initiated plan management. ### Improved * **Onboarding journey** – Smoothed bank setup, delivery toggles, and setup guides so teams can resume exactly where they left off. * **Store settings** – Grouped settings by theme, refreshed analytics, and polished navigation for faster daily workflows. ### Fixed * **Mobile responsiveness** – Tightened guest storefront spacing and padding so menus read cleanly on every device. * **QR exports** – Resolved asset generation issues for print collateral and digital signage. ### New * **Menu editor revamp** – Delivered click-to-edit menus, real-time hours, and search-driven category expansion for faster merchandising. * **Zendesk + Google sign-in** – Embedded support workflows and SSO so operators stay productive without tab sprawl. * **Statements & QR tooling** – Generated monthly statements and on-demand QR codes for marketing placements. ### Improved * **Onboarding controls** – Added fulfilment-specific scheduling, tipping policies, and order-receiving preferences across SMS, email, and tablet. * **Guest ordering 2.0** – Honoured location time zones, deep-linked table-side orders, and refined tip behaviour across channels. ### Fixed * **DoorDash handoff** – Closed the loop on Drive delivery edge cases discovered during pilot launches. * **Global domain routing** – Hardened custom domain flows while we wrapped up Apple Pay validation. ### New * **Search-first storefront** – Launched item and modifier search, popular product carousels, and multi-select modifiers. * **Merchant onboarding** – Introduced the first merchant onboarding, settings, and QR generation flows for digital menus. * **PMS ordering** – Enabled property management system ordering to serve hotel and resort partners. ### Improved * **Design system** – Swapped to the Lato typography stack, standardized imagery, and refined touch targets across mobile. * **Brand storytelling** – Debuted the EMC brand page with refreshed photography and Google Maps distance calculations. ### Fixed * **Tip accuracy** – Corrected gratuity calculations for multi-location partners. * **Modal UX** – Eliminated scrolling bugs and duplicate popular items during fast cart edits. *** Need the original Crave v1 timeline? Check the archived 2021–2023 release notes in the internal handbook or reach out at [hello@craveup.com](mailto:hello@craveup.com). # Source: https://docs.craveup.com/components/cart/cart-item-row Individual cart line item with modifiers, quantity control, and price. # CartItemRow Renders a single line item in the cart, showing the product name, selected modifiers, special instructions, quantity stepper, and line total. ## Preview ## Import ```tsx theme={null} import { CartItemRow } from '@/components/CartItemRow' ``` ## Usage ```tsx theme={null} updateItem(cartItem.id, qty)} onRemove={() => removeItem(cartItem.id)} onEdit={() => editItem(cartItem.id)} /> ``` ## Props The cart item object with product details, modifiers, quantity, and price. Callback fired when quantity is changed via the stepper. Callback fired when the item is removed (quantity reaches 0 or delete is clicked). Optional callback to re-open the product customization screen. ## Behavior * Displays modifier selections as comma-separated tags * Shows special instructions in italics below modifiers * Swipe-to-delete gesture on mobile * Tap on item opens edit mode via `onEdit` # Source: https://docs.craveup.com/components/cart/cart-nav-button Floating or header-mounted button showing cart item count and total. # CartNavButton A button that displays the current cart item count as a badge and optionally shows the cart total. Typically placed in the header or as a floating action button on mobile. ## Preview ## Import ```tsx theme={null} import { CartNavButton } from '@/components/CartNavButton' ``` ## Usage ```tsx theme={null} setIsCartOpen(true)} /> ``` ## Props Number of items currently in the cart. Cart total to display alongside the button text. Callback fired when the button is clicked — typically opens the CartPanel. ## Behavior * Badge animates with a bounce effect when item count changes * Hides when cart is empty (itemCount === 0) * On mobile, renders as a fixed-position floating button at the bottom * On desktop, renders inline in the header navigation # Source: https://docs.craveup.com/components/cart/cart-panel Slide-out panel displaying the current cart contents and order summary. # CartPanel A slide-out side panel that shows all items in the customer's cart, order summary with subtotal/tax/total, and a checkout button. ## Preview ## Import ```tsx theme={null} import { CartPanel } from '@/components/CartPanel' ``` ## Usage ```tsx theme={null} setIsCartOpen(false)} /> ``` ## Props The cart object containing items, totals, and applied discounts. Callback to update an item's quantity. Callback to remove an item from the cart. Callback fired when the checkout button is clicked. Controls the panel's open/closed state. Callback to close the panel. ## Behavior * Slides in from the right with backdrop overlay * Each item row shows name, modifiers summary, quantity stepper, and line total * Order summary section with subtotal, tax, discounts, and total * Empty state with illustration and "Browse menu" CTA * Traps focus when open for accessibility # Source: https://docs.craveup.com/components/cart/cart-related-items Upsell carousel shown in the cart suggesting complementary items. # CartRelatedItems A horizontal carousel displayed within or below the cart panel that suggests related or complementary items to encourage upselling. ## Preview ## Import ```tsx theme={null} import { CartRelatedItems } from '@/components/CartRelatedItems' ``` ## Usage ```tsx theme={null} ``` ## Props Array of suggested products to display. Callback fired when a suggested item's add button is clicked. ## Behavior * Shows compact product cards with image, name, price, and quick-add button * Horizontally scrollable with peek effect * Items are determined by the backend based on cart contents * Hidden when no suggestions are available # Source: https://docs.craveup.com/components/checkout/apply-promo-code Input field for applying discount codes to an order. # ApplyPromoCode A compact input with an apply button that lets customers enter and validate promotional discount codes during checkout. ## Preview ## Import ```tsx theme={null} import { ApplyPromoCode } from '@/components/ApplyPromoCode' ``` ## Usage ```tsx theme={null} ``` ## Props Async callback to validate and apply the promo code. Should return the discount result or throw on invalid codes. Currently applied promo code, if any. Callback to remove the currently applied promo code. ## Behavior * Inline validation with loading state during API call * Shows success message with discount amount on valid code * Shows error message for invalid or expired codes * Displays applied code as a removable chip * Collapses to "Have a promo code?" link when no code is applied # Source: https://docs.craveup.com/components/checkout/checkout-component Top-level checkout orchestrator that manages the multi-step checkout flow. # CheckoutComponent The main checkout container that orchestrates the multi-step checkout flow — customer details, fulfillment selection, payment, and order confirmation. ## Preview ## Import ```tsx theme={null} import { CheckoutComponent } from '@/components/CheckoutComponent' ``` ## Usage ```tsx theme={null} ``` ## Props The finalized cart to check out. The location ID for this order. Callback fired when the order is successfully placed. ## Behavior * Multi-step flow: Customer Details → Fulfillment → Payment → Confirmation * Progress indicator at the top showing current step * Validates each step before allowing progression * Handles payment processing errors with retry * Displays order summary sidebar on desktop # Source: https://docs.craveup.com/components/checkout/customer-details-form Form for collecting customer name, email, and phone number during checkout. # CustomerDetailsForm Collects customer contact information (name, email, phone) as the first step of the checkout flow. ## Preview ## Import ```tsx theme={null} import { CustomerDetailsForm } from '@/components/CustomerDetailsForm' ``` ## Usage ```tsx theme={null} ``` ## Props Pre-filled values from a returning customer or saved session. Callback fired when the form is submitted with valid data. ## Behavior * Validates email format and phone number * Remembers returning customer info via local storage * Phone input with country code selector * Auto-focuses the first empty field # Source: https://docs.craveup.com/components/checkout/order-time-picker Date and time selector for scheduling pickup or delivery orders. # OrderTimePicker A picker that allows customers to choose between "ASAP" ordering or scheduling a future pickup/delivery time within the location's operating hours. ## Preview ## Import ```tsx theme={null} import { OrderTimePicker } from '@/components/OrderTimePicker' ``` ## Usage ```tsx theme={null} ``` ## Props Location ID used to fetch available time slots. The fulfillment method — affects available time windows and lead times. The currently selected order time. Callback fired when the time selection changes. ## Behavior * "ASAP" toggle selected by default with estimated ready time * Date picker for future dates (within location's advance ordering window) * Time slots in 15-minute increments within operating hours * Grays out unavailable slots based on kitchen capacity # Source: https://docs.craveup.com/components/checkout/payment-form Provider-selected secure payment input for Crave checkout. # PaymentForm Renders the server-selected payment experience. An active Square connection uses Square Web Payments; a location without Square uses Stripe Elements. ## Preview ## Behavior * Branches only on `paymentSession.provider`; browser state cannot choose or switch the processor. * Square supports card, Square gift-card partial authorization, authenticated stored cards, optional card saving, and wallet tokens when an eligible Square wallet method supplies the source token. * Stripe Elements remains available only for non-Square locations. * The parent checkout flow owns customer validation, confirmation, errors, idempotent retries, and final redirect. * Raw card and gift-card data stays in provider-hosted fields and is never accepted or logged by Crave application code. Do not render Stripe after a Square session has been created. A missing or unhealthy Square dependency fails the cart closed so the customer cannot be charged by the wrong processor. # Source: https://docs.craveup.com/components/checkout/tip-selector Preset and custom tip amount picker for the checkout flow. # TipSelector Allows customers to select a tip amount from preset percentages or enter a custom amount. Displays the calculated tip based on the order subtotal. ## Preview ## Import ```tsx theme={null} import { TipSelector } from '@/components/TipSelector' ``` ## Usage ```tsx theme={null} ``` ## Props Order subtotal used to calculate percentage-based tips. The currently selected tip amount in cents. Callback fired when the tip selection changes. Array of percentage presets to display as quick-select buttons. ## Behavior * Preset percentage buttons with calculated dollar amounts * "Custom" option reveals a numeric input * "No tip" option to explicitly set tip to \$0 * Active preset is highlighted with primary accent # Source: https://docs.craveup.com/components/fulfillment/delivery Delivery address form with validation and delivery zone checking. # Delivery Collects and validates a delivery address, checks if the address falls within the merchant's delivery zone, and displays estimated delivery time and fees. ## Preview ## Import ```tsx theme={null} import { Delivery } from '@/components/Delivery' ``` ## Usage ```tsx theme={null} ``` ## Props The location ID to check delivery zones against. Callback fired when a valid delivery address is confirmed. ## Behavior * Google Places autocomplete for address input * Validates address against the location's delivery zones * Shows "Outside delivery area" error for out-of-range addresses * Displays delivery fee and estimated time for valid addresses * Optional delivery instructions text field # Source: https://docs.craveup.com/components/fulfillment/fulfilment-method-handler Route-level handler that renders the appropriate fulfillment flow based on order type. # FulfilmentMethodHandler A routing component that determines and renders the correct fulfillment flow (delivery, table-side, room service) based on the ordering session's fulfillment method. ## Preview ## Import ```tsx theme={null} import { FulfilmentMethodHandler } from '@/components/FulfilmentMethodHandler' ``` ## Usage ```tsx theme={null} ``` ## Props The current ordering session containing the selected fulfillment method. Callback fired when fulfillment details are confirmed by the customer. ## Behavior * Reads the fulfillment method from the ordering session * Renders `Delivery`, `TableSide`, or `RoomService` sub-component accordingly * Falls back to pickup if no specific method is set * Passes collected details (address, table number, room number) up via callback # Source: https://docs.craveup.com/components/fulfillment/room-service Room number and guest name input for hotel room service ordering. # RoomService Collects room number and guest name for hotel room service fulfillment. Validates the room number against the property's room directory. ## Preview ## Import ```tsx theme={null} import { RoomService } from '@/components/RoomService' ``` ## Usage ```tsx theme={null} ``` ## Props The location (property) ID to validate room numbers against. Callback fired when room details are confirmed. ## Behavior * Room number input with validation * Guest name field (required for room charge authorization) * Optional delivery instructions * Auto-populated when accessed via in-room QR code # Source: https://docs.craveup.com/components/fulfillment/table-side Table number input for dine-in table-side ordering. # TableSide A simple input for customers to enter their table number for dine-in table-side ordering. Validates the table number against the location's configured tables. ## Preview ## Import ```tsx theme={null} import { TableSide } from '@/components/TableSide' ``` ## Usage ```tsx theme={null} ``` ## Props The location ID to validate table numbers against. Callback fired when a valid table number is submitted. ## Behavior * Numeric input with large, touch-friendly buttons * Validates table number exists for the location * Shows error for invalid table numbers * Auto-populated when accessed via QR code scan with table parameter # Installation Source: https://docs.craveup.com/components/installation Install dependencies and structure your app for Crave UI components. Crave UI components are local React components that sit inside your storefront app. They pair with the [Storefront SDK](/getting-started/storefront-sdk), Tailwind CSS, and shadcn/ui conventions so you can own the markup, styling, and ordering flows. Choose the setup that matches your starting point. Configure the API origin, merchant slug, and location ID without a browser credential. Start from a Crave storefront template with components and SDK wiring included. Add the SDK, shared UI dependencies, and component source to an app you already own. See where components, API helpers, and shared utilities should live. ## Use the CLI For new storefronts, start with the Crave CLI. The generated template includes the Storefront SDK, component files, Tailwind setup, and environment variables. ```bash theme={null} npx @craveup/cli init ``` Then run the generated app: ```bash theme={null} cd crave-storefront pnpm install pnpm dev ``` The template is the recommended source of truth for component file structure because the components are meant to be edited in your app. ## Existing app For an existing React or Next.js app, install the SDK first: ```bash theme={null} pnpm add --save-exact @craveup/storefront-sdk@2.0.1 ``` If your app does not already use shadcn/ui, initialize it before copying component source: ```bash theme={null} pnpm dlx shadcn@latest init ``` Install the shared UI utilities used by the component examples: ```bash theme={null} pnpm add class-variance-authority clsx tailwind-merge lucide-react ``` Then copy the Crave component source from a generated storefront template into your app's `components` directory and adapt imports to match your aliases. ## Project structure Use a structure that keeps UI, data access, and ordering state separate: ```txt theme={null} src/ app/ menu/ cart/ checkout/ components/ menu/ cart/ checkout/ fulfillment/ modifiers/ post-order/ ui/ lib/ storefront-client.ts utils.ts types/ storefront.ts ``` ## Configure the SDK Create a shared Storefront SDK client and import it from your data loaders, hooks, or server actions. Get the published location ID and merchant slug from Crave Business Manager. Configure the public API origin explicitly. ```ts filename="src/lib/storefront-client.ts" theme={null} import { createStorefrontClient } from "@craveup/storefront-sdk"; export const storefrontClient = createStorefrontClient({ baseUrl: process.env.NEXT_PUBLIC_CRAVEUP_API_URL!, sessionStore, getAuthToken, }); ``` Add the required environment variables: ```env filename=".env.local" theme={null} NEXT_PUBLIC_CRAVEUP_API_URL=https://api.craveup.com NEXT_PUBLIC_CRAVEUP_LOCATION_ID=loc_abc123 NEXT_PUBLIC_CRAVEUP_MERCHANT_SLUG=demo-cafe ``` ## Import components Crave component docs use local imports so you can customize the implementation: ```tsx theme={null} import { CartPanel } from "@/components/cart/cart-panel"; import { Item } from "@/components/menu/item"; import { MenuSwitcher } from "@/components/menu/menu-switcher"; ``` Keep the component source in your app, then pass SDK data and callbacks from your own routes or state layer. ## Next steps Browse the full component catalog. Configure the typed API client used by the components. Fetch menu data and render categories, products, and modifiers. Wire cart creation, item updates, discounts, and checkout handoff. # Source: https://docs.craveup.com/components/menu/category-tabs Sticky tab navigation for browsing menu categories. # CategoryTabs A horizontal tab bar that displays menu categories with an active indicator. Sticks to the top of the viewport as the user scrolls through the menu. ## Preview ## Import ```tsx theme={null} import { CategoryTabs } from '@/components/CategoryTabs' ``` ## Usage ```tsx theme={null} scrollToCategory(id)} /> ``` ## Props Array of menu categories to display as tabs. The ID of the currently active/visible category. Callback fired when a tab is clicked — typically scrolls to the category section. ## Behavior * Sticks to the top of the viewport below the header on scroll * Highlights the active category based on scroll position * Horizontally scrollable on mobile for long category lists * Smooth-scrolls the active tab into view automatically # Source: https://docs.craveup.com/components/menu/item Unified product card that renders vertical or horizontal layouts. # Item A versatile product card component that displays a menu item with image, name, description, price, and a quick-add button. Supports both vertical (grid) and horizontal (list) layouts. ## Preview ## Import ```tsx theme={null} import { Item } from '@/components/Item' ``` ## Usage ```tsx theme={null} {/* Vertical layout for grid view */} {/* Horizontal layout for list view */} ``` ## Props The product object containing name, description, price, images, and modifier groups. Controls the card layout direction. Callback fired when the add button is clicked. If the product has modifiers, this should open the `ProductDescriptionScreen`. ## Behavior * Truncates long descriptions with ellipsis * Shows product image with fallback placeholder * Quick-add button bypasses modifier screen when no modifiers exist * Displays "From \$X.XX" when modifier groups affect pricing # Source: https://docs.craveup.com/components/menu/item-carousel Horizontal carousel for featured or recommended menu items. # ItemCarousel A horizontally scrollable carousel that displays a row of product cards. Used for featured items, recommendations, or category highlights. ## Preview ## Import ```tsx theme={null} import { ItemCarousel } from '@/components/ItemCarousel' ``` ## Usage ```tsx theme={null} ``` ## Props Array of products to display in the carousel. Optional heading displayed above the carousel. Callback fired when a carousel item is clicked. ## Behavior * Touch-friendly horizontal scrolling on mobile * Peek effect showing partial next card to indicate scrollability * Renders each item using the vertical `Item` component # Source: https://docs.craveup.com/components/menu/menu-switcher Dropdown to switch between multiple menus with availability status. # MenuSwitcher A dropdown selector that allows customers to switch between multiple menus (e.g., Breakfast, Lunch, Dinner). Displays availability status based on operating hours. ## Preview ## Import ```tsx theme={null} import { MenuSwitcher } from '@/components/MenuSwitcher' ``` ## Usage ```tsx theme={null} setActiveMenu(menuId)} /> ``` ## Props Array of available menus for the location. The ID of the currently selected menu. Callback fired when a different menu is selected. ## Behavior * Shows "Available now" or "Available at 11:00 AM" based on daypart logic * Grays out menus outside their operating hours * Auto-selects the first available menu on mount # Source: https://docs.craveup.com/components/modifiers/item-counter-button Increment/decrement stepper for adjusting item or modifier quantities. # ItemCounterButton A compact stepper control with minus/plus buttons and a quantity display. Used for adjusting item quantities in the cart and modifier option counts. ## Preview ## Import ```tsx theme={null} import { ItemCounterButton } from '@/components/ItemCounterButton' ``` ## Usage ```tsx theme={null} setQuantity(q => q + 1)} onDecrement={() => setQuantity(q => q - 1)} min={1} max={99} /> ``` ## Props The current quantity value. Callback fired when the plus button is clicked. Callback fired when the minus button is clicked. Minimum allowed value. Decrement button is disabled at this value. Maximum allowed value. Increment button is disabled at this value. ## Behavior * Disables minus button when count reaches `min` * Disables plus button when count reaches `max` * Animates the count value on change * Compact variant available for inline use in modifier options # Source: https://docs.craveup.com/components/modifiers/modifier-group Renders a group of modifier options (radio, checkbox, or counter) for product customization. # ModifierGroup Displays a single modifier group (e.g., "Choose your size", "Add toppings") with its options. Supports radio (single-select), checkbox (multi-select), and counter (quantity) input types. ## Preview ## Import ```tsx theme={null} import { ModifierGroup } from '@/components/ModifierGroup' ``` ## Usage ```tsx theme={null} ``` ## Props The modifier group object containing name, options, min/max selections, and input type. Currently selected modifiers for this group. Callback fired when modifier selections change. ## Behavior * Enforces min/max selection constraints with visual feedback * Radio groups auto-deselect previous option * Counter inputs respect per-option quantity limits * Displays price adjustments next to each option (e.g., "+\$1.50") # Source: https://docs.craveup.com/components/modifiers/product-description-screen Full-screen product detail view with modifiers, quantity, and add-to-cart action. # ProductDescriptionScreen A full-screen overlay that shows detailed product information including image, description, modifier groups, quantity selector, and an add-to-cart button with running total. ## Preview ## Import ```tsx theme={null} import { ProductDescriptionScreen } from '@/components/ProductDescriptionScreen' ``` ## Usage ```tsx theme={null} ``` ## Props The product to display, including all modifier groups. Callback fired when the user confirms selections and adds the item to cart. Callback to dismiss the screen. ## Behavior * Displays product hero image with parallax scroll effect * Renders all modifier groups using `ModifierGroup` components * Shows running price total that updates as modifiers are selected * Validates required modifier groups before enabling the add button * Supports special instructions text input at the bottom # Source: https://docs.craveup.com/components/modifiers/special-instructions Text input for customer notes and special requests on a menu item. # SpecialInstructions A text area input that allows customers to add special instructions or notes to a menu item (e.g., "no onions", "extra sauce on the side"). ## Preview ## Import ```tsx theme={null} import { SpecialInstructions } from '@/components/SpecialInstructions' ``` ## Usage ```tsx theme={null} ``` ## Props The current instructions text. Callback fired when the text changes. Placeholder text shown when the input is empty. Maximum character limit for the instructions. ## Behavior * Character counter displayed when approaching the limit * Expandable text area that grows with content * Optional — collapses to a single-line prompt when empty # Components Source: https://docs.craveup.com/components/overview Pre-built React components for menu browsing, cart management, checkout, and fulfillment. Crave provides restaurant-specific React components that integrate with your [shadcn/ui](https://ui.shadcn.com) design system. Drop them into your storefront to handle menu display, cart logic, checkout, and fulfillment — then style them to match your brand. Set up dependencies, project structure, and component source in your storefront. Add the public API origin, merchant slug, and location ID without a browser credential. ## Menu & Products Display menus, categories, and product cards. Sticky tab navigation for menu categories with active indicator. Dropdown to switch between menus (lunch, dinner) with availability. Product card — renders vertical or horizontal with quick-add. Carousel for featured or recommended menu items. ## Modifiers Product customization — sizes, toppings, special instructions. Renders modifier groups (e.g., size, toppings) with selection state. Full-screen modal for product details, modifiers, and add-to-cart. Textarea for customer special requests and dietary notes. Quantity selector with increment/decrement buttons. ## Cart Manage the shopping cart. Side drawer showing cart contents with checkout CTA. Header button showing item count — opens the cart panel. Individual cart item with modifiers, quantity controls, and price. Recommended upsell items shown inside the cart. ## Checkout Customer details, payments, and order submission. Main checkout layout orchestrating all checkout sections. Form for name, email, and phone with validation. Stripe PaymentElement wrapper for card and payment methods. Interactive tip selection with presets and custom input. ASAP or scheduled pickup/delivery time selector. Promo code input with validation and discount display. ## Fulfillment Order fulfillment method selection — delivery, pickup, table-side, room service. Routes to the correct fulfillment UI based on method. Address picker and delivery details. Table number selection for dine-in service. Room number and guest name input for hotel room service. ## Post-Order Order confirmation and feedback. Thank-you screen with order details and next steps. Star rating component for post-order feedback. # Source: https://docs.craveup.com/components/post-order/experience-rating Post-order feedback widget for rating the ordering experience. # ExperienceRating A feedback widget shown after order completion that lets customers rate their experience with star ratings and optional text feedback. ## Preview ## Import ```tsx theme={null} import { ExperienceRating } from '@/components/ExperienceRating' ``` ## Usage ```tsx theme={null} ``` ## Props The order ID to associate the feedback with. Callback fired when the customer submits their rating. Callback fired when the customer dismisses the rating prompt. ## Behavior * 5-star rating with tap/click interaction * Optional text feedback field appears after rating * Category tags for specific feedback (food quality, speed, accuracy) * Submits to the analytics events API * Only shown once per order # Source: https://docs.craveup.com/components/post-order/order-confirmation Post-checkout screen showing order details, status, and estimated ready time. # OrderConfirmation The confirmation screen displayed after a successful order placement. Shows the order number, item summary, estimated ready/delivery time, and real-time status updates. ## Preview ## Import ```tsx theme={null} import { OrderConfirmation } from '@/components/OrderConfirmation' ``` ## Usage ```tsx theme={null} ``` ## Props The placed order object with items, totals, status, and fulfillment details. Optional callback to navigate to the order tracking view. ## Behavior * Displays order number with a checkmark success animation * Shows estimated ready time or delivery ETA * Lists ordered items with quantities and prices * Real-time status updates via polling or WebSocket * "Track your order" CTA for delivery orders * "Order again" button to reorder the same items # CLI Quickstart Source: https://docs.craveup.com/getting-started/cli-quickstart Use the Crave CLI to scaffold, configure, and launch a storefront template. The Crave CLI bootstraps a production-ready storefront in a few minutes. It installs dependencies, wires environment variables, and can even start the dev server for you. The CLI publishes as **`@craveup/cli`**. The older unscoped `craveup` package on npm is unmaintained and stops at 1.0.3 — install the scoped name. The installed command is still `craveup`, so `craveup login` and the rest are unchanged. ## Install & run Run the CLI anywhere you want to create the project directory: ```bash theme={null} npx @craveup/cli init ``` The wizard will guide you through: * Choosing a template (`storefront-default`, Tomodachi Sushi, or Xichuan Noodles) * Selecting the published organization and location * Naming the project directory * Installing dependencies with your detected package manager * Optionally starting `pnpm dev` so you can preview immediately ## Sign in Sign in before scaffolding so the CLI can discover the organizations and locations your account can manage: ```bash theme={null} npx @craveup/cli login # browser OAuth; sign in or create a free account npx @craveup/cli whoami # confirm the active account npx @craveup/cli logout # remove local credentials ``` The session is stored in the macOS keychain, falling back to `~/.config/craveup/credentials.json`, and is **shared with the [CraveUp MCP server](/guides/ai/craveup-mcp-server)** — signing in through either one satisfies the other. On a headless or remote machine, use `craveup login --device` to pair with a code you open on any other device. ## Non-interactive usage Prefer automation or CI pipelines? Use flags to skip prompts. | Flag | Description | | - | - | | `--template ` | Force a template (`storefront-default`, `tomodachi-sushi`, or `xichuan-noodles`) | | `--dir ` | Set the target directory name | | `--api-key ` | Optional server-only CLI credential for non-interactive private API access | | `--location-id ` | Provide the location ID for single-location templates | | `--org-slug ` | Provide the organization slug for organization templates | | `--skip-install` | Scaffold files without running the package manager | | `--force` | Overwrite an empty directory without confirmation | Example: ```bash theme={null} npx @craveup/cli init \ --template storefront-default \ --dir crave-storefront \ --org-slug my-restaurant-group \ --skip-install ``` ## Environment variables The CLI creates `.env.local` with placeholders. Update them before running the app: ```env theme={null} # Public Storefront API origin. No browser API key is used. NEXT_PUBLIC_CRAVEUP_API_URL=https://api.craveup.com NEXT_PUBLIC_CRAVEUP_LOCATION_ID=loc_abc123 NEXT_PUBLIC_CRAVEUP_MERCHANT_SLUG=my-restaurant-group ``` > Tip: `CRAVEUP_DASHBOARD_URL`, `CRAVEUP_PARTNER_API_URL`, and a private `CRAVEUP_API_KEY` are CLI/server concerns. They are never written to a public storefront environment variable. The merchant and location variables are standalone/local overrides. A hosted `cravejs-storefront-template` resolves the merchant from its controlled host/subdomain and the location from the `[locationId]` route instead of duplicating tenant selection in production environment variables. The CLI does not write `NEXT_PUBLIC_STOREFRONT_URL`; the template derives its canonical metadata origin from the request host. Add that optional override manually only when the deployed metadata origin must differ from the request host. ## Project anatomy Every template ships with: * **Next.js 16.3 + App Router** for modern server-first rendering * **`@craveup/storefront-sdk@2.0.1`** pinned and preconfigured in `src/lib/storefront-client.ts` * **Typed API helpers** co-located with app routes * **Component library** for menu browsing, cart management, and checkout Explore the generated repository after the CLI finishes: ```bash theme={null} cd crave-storefront pnpm install # if you skipped installation pnpm dev --port 3000 ``` You now have a fully functional storefront backed by live Crave data. Continue to the [Storefront SDK guide](./storefront-sdk) to integrate custom flows. # Dart and Flutter Storefront SDK Source: https://docs.craveup.com/getting-started/flutter-storefront-sdk Build typed Flutter storefronts against the public Crave Storefront API. `crave_storefront_sdk` is the official Dart client for Flutter mobile, web, and desktop storefronts. It exposes the same reviewed `/api/v1/storefront` contract as [`@craveup/storefront-sdk`](/getting-started/storefront-sdk), with Dart-native models, cancellation, timeouts, and typed exceptions. It contains no UI, Flutter, or native runtime dependency. ## Install the exact release ```yaml pubspec.yaml theme={null} dependencies: crave_storefront_sdk: 0.2.0 ``` Run `flutter pub get`. Dart-only consumers can run `dart pub get` instead. ## Configure one environment and merchant ```dart theme={null} import 'package:crave_storefront_sdk/crave_storefront_sdk.dart'; final client = CraveStorefrontClient( baseUri: Uri.parse('https://api.craveup.com'), merchantSlug: 'downtown-pizza', sessionStore: secureSessionStore, customerTokenProvider: () async => currentCustomerJwt, ); ``` Use the exact sandbox API origin provided for your account in sandbox builds. Production and sandbox are separate deployments with separate environment variables, credentials, and data; do not add a runtime environment switch to a production app. The client accepts plain HTTP only for exact loopback hosts during local development. `StorefrontSessionStore` is application-owned. In production, implement it with encrypted platform storage and isolate records by the supplied API-origin, merchant, and location scope. The included `InMemoryStorefrontSessionStore` is only for tests and ephemeral demos. Keep customer JWTs in your authentication layer, not in the cart session store. ## Check ordering readiness Check availability before presenting an ordering action. This anonymous GET does not create a cart: ```dart theme={null} final readiness = await client.locations.getOrderingReadiness( locationId, fulfillmentMethod: FulfillmentMethod.takeout, ); switch (readiness) { case OrderingReady(): showStartOrder(); case OrderingUnavailable(:final reason): showOrderingUnavailable(reason); } ``` ## Handle typed failures ```dart theme={null} try { final menu = await client.menus.getForLocation(locationId, menuOnly: true); renderMenu(menu); } on StorefrontApiException catch (error) { reportSafeFailure(error.code, error.status, error.requestId); } on StorefrontTimeoutException { showRetryMessage(); } on StorefrontRequestCancelledException { // The owning screen was disposed or the shopper cancelled the action. } ``` The exception contract excludes resolved URLs, headers, request bodies, customer data, and payment values. Cart mutations are never retried implicitly. After `CART_CONFLICT`, re-read the cart, reconcile the new revision, and let the shopper confirm the next mutation. Close the client when its application-level owner is disposed: ```dart theme={null} client.close(); ``` Install the package, configure secure session storage, load a menu, and start an order. # Build a Restaurant Ordering App in Minutes Source: https://docs.craveup.com/getting-started/introduction Scaffold a branded storefront, connect menus, carts, checkout, fulfillment, and payments, then ship against the Crave API. Crave gives food and beverage developers the ordering layer they usually have to stitch together themselves: menu data, modifiers, cart state, checkout, fulfillment, payments, analytics, reusable UI components, and APIs that work across web and mobile apps. Crave.js is the storefront developer surface inside **Crave Cloud**, a managed platform for franchise ordering, identity boundaries, loyalty integrations, payment routing, and multi-location operations. ## Start in 5 minutes The fastest path is to scaffold a storefront, add its public API origin and restaurant identifiers, and run it locally. ```bash npm theme={null} npx @craveup/cli init cd crave-storefront npm run dev ``` ```bash pnpm theme={null} pnpm dlx craveup init cd crave-storefront pnpm dev ``` ```bash yarn theme={null} yarn dlx craveup init cd crave-storefront yarn dev ``` Open [http://localhost:3000](http://localhost:3000), then use the generated `.env.local` to add the public API origin, merchant slug, and location ID. A storefront never receives a Crave API key. Connect the generated app to published merchant, location, menu, and cart resources without a browser credential. Already have an app? Skip the scaffold and install the exact `@craveup/storefront-sdk@2.0.1` release directly. ## Running? Read this next Understand how Crave.js fits into the broader franchise platform. Register exact browser origins and keep private credentials out of storefront bundles. Connect the CraveUp MCP server and use a prebuilt storefront prompt before handing work to an AI agent. Learn the CLI flags, templates, and generated project structure. Connect your UI to menus, carts, checkout, and analytics with typed helpers. Call Crave endpoints from any platform using standard HTTP requests. ## Where this fits Learn what is available to developers and what stays managed by Crave Cloud. Combine the CLI, Storefront SDK, REST APIs, and UI components for a branded ordering experience. Give Cursor, Claude, ChatGPT, Codex, or another agent the right Crave context before it edits your app. ## Quickstarts & Tutorials Explore our end-to-end tutorials and getting started guides for different application stacks. Build a storefront with Next.js 16.3 App Router and the Crave SDK. Get started with React + Vite and the Crave SDK. Build a storefront with plain JavaScript and the REST API. Create a mobile ordering app with Expo and the Crave SDK. ## UI Components Crave's prebuilt UI components give you a fully-functional ordering experience in minutes — menus, carts, checkout, and fulfillment out of the box. Browse all available components and learn how they work together. The TypeScript SDK that powers the components and data fetching. ## SDK & API Reference Dig into our SDK reference documentation and REST API endpoints. Everything you need to integrate Crave into any application. API overview, authentication, endpoints, and response formats. Merchants, locations, menus, carts, and the ordering lifecycle. ## Explore by feature Crave supports the full ordering lifecycle. Pick a feature to dive deep. Display categories, products, and modifiers. Support multiple menus with scheduled availability. Manage the cart lifecycle — add items, apply modifiers, handle discounts, and submit orders. Accept payments with Stripe Connect. Handle payment intents, test cards, and error recovery. Configure takeout, delivery, table-side ordering, and room service for your locations. Track funnel events across the ordering flow and measure conversion rates. Review the current availability boundary before designing an event-driven integration. ## Explore by framework Server-side rendering with the App Router. Fetch menus on the server, manage carts on the client. Client-side React with Vite. Build a custom SPA storefront with the SDK. No framework required. Call the REST API directly from plain JavaScript. Build native mobile ordering apps with Expo and the Crave SDK. ## Beyond the basics Customize colors, fonts, and component styles to match your restaurant's brand. Use test mode, mock data, and example test suites to validate your integration. Deploy to Vercel, Netlify, Docker, or custom hosting with production best practices. Handle every error the API can return with status codes, retry strategies, and user messages. ## Community Connect with the Crave community on Slack. Reach out to our team directly for help with your integration. # REST API Essentials Source: https://docs.craveup.com/getting-started/rest-api Call the public Crave Storefront API directly from web, mobile, or server applications. The public Storefront API is rooted at: ```text theme={null} https://api.craveup.com/api/v1/storefront ``` Published merchant, location, menu, and product reads are anonymous. A new ordering session returns a cart capability. Send that capability only in `X-Cart-Token` for the matching cart, use the current ETag in `If-Match` for mutations, and give every write an `Idempotency-Key`. Signed-in customer resources use `Authorization: Bearer `. ## Fetch a published menu ```bash theme={null} curl "https://api.craveup.com/api/v1/storefront/locations/loc_123/menus?orderDate=2026-08-08&orderTime=18:30" ``` ## Start an ordering session ```bash theme={null} curl -X POST "https://api.craveup.com/api/v1/storefront/locations/loc_123/ordering-sessions" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: session_01" \ -d '{"fulfillmentMethod":"takeout"}' ``` The optional `marketplaceId` is a source-attribution label such as `"web"` or `"mobile"`, not a location ID. The route already supplies the location scope. Store `cart.id`, `cartAccessToken`, and `cart.revision` together in tab-scoped browser storage or the mobile platform's secure storage. ## Add an item ```bash theme={null} curl -X POST "https://api.craveup.com/api/v1/storefront/locations/loc_123/carts/cart_456/items" \ -H "Content-Type: application/json" \ -H "X-Cart-Token: $CART_ACCESS_TOKEN" \ -H 'If-Match: "cart-0"' \ -H "Idempotency-Key: add_item_01" \ -d '{"productId":"prod_margherita","quantity":1,"selections":[],"itemUnavailableAction":"remove_item"}' ``` The response includes a new ETag. Use that value for the next mutation. If the API returns `CART_CONFLICT`, fetch the cart with the capability, update local state and revision, and require an explicit user retry. ## Authentication boundaries | Resource | Credential | | - | - | | Published catalog | None | | Cart and checkout | Matching cart capability | | Customer profile, orders, addresses, saved payments, loyalty | Customer JWT | | Public receipt | Receipt capability from the URL fragment or customer JWT | | Admin and partner APIs | Server-only credential; never a storefront client variable | Do not put capabilities, JWTs, receipt tokens, or payment secrets in query strings or logs. Configure the API CORS allowlist with exact production and preview storefront origins. Explore the [Storefront REST reference](/api-reference/overview/api-overview) for endpoint schemas. # Storefront SDK Source: https://docs.craveup.com/getting-started/storefront-sdk Use the typed direct client for public catalog, cart, customer, checkout, receipt, and loyalty flows. `@craveup/storefront-sdk@2.0.1` is the supported TypeScript client for the public API at `/api/v1/storefront`. Version 2 has no browser API-key option and is a deliberate major-version migration from the published 1.x contract. ## Install and configure ```bash theme={null} pnpm add --save-exact @craveup/storefront-sdk@2.0.1 ``` Create separate server and browser clients. A server client has no session or JWT provider; use it only for anonymous published calls: ```ts theme={null} import { createStorefrontClient } from "@craveup/storefront-sdk"; export const storefrontServer = createStorefrontClient({ baseUrl: process.env.NEXT_PUBLIC_CRAVEUP_API_URL!, }); ``` A browser client owns the caller-provided, tab-scoped stores: ```ts theme={null} import { createStorefrontClient } from "@craveup/storefront-sdk"; import { customerTokenStore, sessionStore } from "./storefront-session"; export const storefrontBrowser = createStorefrontClient({ baseUrl: process.env.NEXT_PUBLIC_CRAVEUP_API_URL!, sessionStore, getAuthToken: () => customerTokenStore.get(), defaultTimeoutMs: 10_000, }); ``` | Option | Required | Purpose | | - | - | - | | `baseUrl` | Yes | Exact Crave API origin; remote origins must use HTTPS. | | `sessionStore` | For carts | Caller-owned storage for the cart ID, capability, location, and revision. | | `getAuthToken` | For customer flows | Supplies the merchant-bound customer JWT. | | `fetch` | No | Custom fetch implementation for non-browser runtimes. | | `defaultTimeoutMs` | No | Timeout for auth-token resolution and the network response. | The client ignores caller-supplied authorization credentials and obtains customer auth only from `getAuthToken`. Private API keys are not a Storefront SDK option. The exact typed method surface is the complete public JSON transport API; raw request helpers are not exported, and the navigation-only hosted-storefront redirect is deliberately unwrapped. Do not pass the browser client, its store, or its token provider into a Server Component, server action, or route handler. Every remote `baseUrl` must use HTTPS. Plain HTTP is accepted only for the exact loopback hosts `localhost`, `127.0.0.1`, and `[::1]`; a phone-accessible LAN host such as `192.168.x.x` is not loopback. Redirect responses are rejected rather than followed. Async `getAuthToken` resolution uses the same timeout as the network response. `defaultTimeoutMs` and a per-call `timeoutMs` start before the token lookup, so a provider that does not settle produces a `StorefrontTimeoutError` without sending a request. Scope stored data as follows: | Record | Required scope | Browser persistence | Mobile persistence | | - | - | - | - | | Guest cart session | API environment + merchant + location | Versioned `sessionStorage` | `expo-secure-store` | | Customer JWT | API environment + merchant | Versioned `sessionStorage` | `expo-secure-store` | | Receipt capability | API environment + merchant + receipt ID | Versioned `sessionStorage` | Only when resuming | Reject malformed or unknown stored versions instead of casting unvalidated JSON. The [Next.js](/quickstarts/nextjs), [React](/quickstarts/react), and [Expo](/quickstarts/mobile) quickstarts provide complete adapters. Within one client instance, the SDK serializes session-store mutations per location, including async SecureStore adapters. It persists revisions monotonically, coalesces identical concurrent ordering-session starts, and serializes different starts for the same location. Separate client instances or processes need coordination from the application or storage adapter. ## Authorization model * Published merchant, location, menu, product, distance, order-time, and gratuity calls are anonymous and never receive the customer JWT. * `orderingSessions.start` returns a purpose-limited cart capability and saves it through `sessionStore`. * Cart calls attach the capability, current revision, and an idempotency key where required. * Customer profile, order, address, saved-payment, and loyalty history calls use the customer JWT. * Receipt access uses `receiptToken` and sends it only as `X-Receipt-Token`. Use tab-scoped storage for browser capabilities. Clear the guest capability after claim, deletion, expiry, or terminal checkout handling; after claim, keep only the authoritative cart revision if customer-authenticated access continues. Clear the merchant-scoped JWT on logout and a receipt capability after terminal display or expiry. Never put capabilities or customer JWTs in URLs, public environment variables, logs, source maps, HTML, or analytics. ## Catalog and ordering ```ts theme={null} const merchant = await storefrontServer.merchant.getBySlug("demo-cafe"); const location = await storefrontServer.locations.getById("loc_123"); const menus = await storefrontServer.menus.list(location.id, { menuOnly: true, }); const readiness = await storefrontServer.locations.getOrderingReadiness( location.id, "takeout", ); if (!readiness.ready) throw new Error(readiness.reason); const { cart } = await storefrontBrowser.orderingSessions.start(location.id, { fulfillmentMethod: "takeout", }); const updated = await storefrontBrowser.cart.addItem(location.id, cart.id, { productId: "prod_123", quantity: 1, selections: [], itemUnavailableAction: "remove_item", }); ``` `locations.getOrderingReadiness()` is an anonymous, side-effect-free GET. Use it to check a fulfillment method without creating a cart; omit the second argument to use the `takeout` default. ## Checkout and terminal result ```ts theme={null} const paymentSession = await storefrontBrowser.checkout.createPaymentSession( locationId, cartId, { includeCustomerContext: true }, ); const result = await storefrontBrowser.checkout.getOrderResult( locationId, cartId, ); switch (result.state) { case "payment_pending": case "order_pending": break; // poll with bounded backoff case "completed": console.log(result.order.id); break; case "failed": console.error(result.code); break; } ``` The PaymentIntent client secret is browser-usable but still sensitive: keep it in memory and do not log it or place it in a URL. Use `includeCustomerContext: true` only when a signed-in shopper still has a guest capability and payment creation needs Stripe customer association. Omit it for guest checkout. A claimed cart already uses the customer JWT because its guest capability has been cleared. ## Customer authentication ```ts theme={null} const challenge = await storefrontBrowser.customer.login({ merchantSlug: "demo-cafe", identifierString: "alex@example.com", }); const { token } = await storefrontBrowser.customer.verifyOtp({ merchantSlug: "demo-cafe", identifierString: "alex@example.com", customerName: "Alex", lastName: "Customer", methodId: challenge.methodId, otp: "123456", }); ``` After storing the JWT for the current tab, use `customer.getProfile`, `customer.orders`, `customer.addresses`, `customer.savedPayments`, `loyalty.ledger`, and `loyalty.claims`. ## Receipts ```ts theme={null} const receipt = await storefrontBrowser.receipts.get(receiptId, { receiptToken, }); ``` Receipt links place the capability in the URL fragment. Capture it, immediately remove the fragment with `history.replaceState`, and keep it only in tab-scoped storage for that receipt. ## Errors and concurrency ```ts theme={null} import { StorefrontApiError, StorefrontClientStateError, StorefrontProtocolError, StorefrontTimeoutError, } from "@craveup/storefront-sdk"; try { await storefrontBrowser.cart.updateGratuity(locationId, cartId, { percentage: "18", }); } catch (error) { if (error instanceof StorefrontApiError) { console.error(error.status, error.code, error.requestId); if (error.retryAfterMs !== undefined) scheduleRetry(error.retryAfterMs); } else if (error instanceof StorefrontClientStateError) { console.error(error.code); } else if (error instanceof StorefrontProtocolError) { console.error(error.reason, error.method, error.routeTemplate); } else if (error instanceof StorefrontTimeoutError) { console.error(error.method, error.routeTemplate); } } ``` `StorefrontClientStateError` is a typed local preflight failure. For example, `CUSTOMER_AUTH_REQUIRED` means a customer-authenticated method was called without an available JWT, so no network request was sent. `StorefrontProtocolError.reason` is `EMPTY_RESPONSE` or `INVALID_JSON` for a successful response that violates the JSON contract. `retryAfterMs` is a bounded millisecond delay parsed from `Retry-After`; it is `undefined` when the API did not provide a usable value. On `CART_CONFLICT`, fetch the cart again before presenting a retry. Reuse an explicit idempotency key when your application deliberately retries the same logical mutation. ## Navigation-only redirect `GET /locations/{locationId}/redirect` is a navigation-only REST operation and is intentionally not part of the typed SDK method surface. Navigate a browser or native in-app browser to it directly when entering Crave's hosted storefront; do not fetch it as JSON with the Storefront client. SDK requests reject redirect responses and never follow them. ## Browser-origin onboarding Register every exact browser origin with Crave before testing from a browser. The origin includes its scheme, hostname, and any non-default port; local, preview, and production origins are separate registrations. Wildcards are not accepted. Native mobile requests normally omit the browser `Origin` header, but an Expo web deployment still needs registration. # Accept Payments Source: https://docs.craveup.com/guides/accept-payments Use the protected Crave payment session selected for the restaurant. Crave selects the restaurant's connected payment provider server-side. Square is selected whenever it is connected; Stripe is used only where Square is disconnected. ## Create a payment session ```ts theme={null} const payment = await storefront.checkout.createPaymentSession( locationId, cartId, { idempotencyKey: crypto.randomUUID() }, ); ``` The SDK sends the cart capability, current revision, and idempotency key. A raw REST request must send the same headers: ```bash theme={null} curl -X POST "https://api.craveup.com/api/v1/storefront/locations/loc_123/carts/cart_456/payment-session" \ -H "X-Cart-Token: $CART_ACCESS_TOKEN" \ -H 'If-Match: "cart-4"' \ -H "Idempotency-Key: payment_01" ``` If `payment.provider` is `SQUARE`, tokenize with Square Web Payments and send the token to `checkout.confirmPayment`. If it is `STRIPE`, confirm its client secret with Stripe Elements. ## Confirm a Stripe session with Stripe Elements ```tsx theme={null} import { PaymentElement, useElements, useStripe, } from "@stripe/react-stripe-js"; export function PaymentForm() { const stripe = useStripe(); const elements = useElements(); async function submit(event: React.FormEvent) { event.preventDefault(); if (!stripe || !elements) return; const { error } = await stripe.confirmPayment({ elements, confirmParams: { return_url: `${window.location.origin}/order/confirmation`, }, }); if (error) showPaymentError(error.message); } return (
); } ``` Use Stripe publishable configuration in the client. Stripe secret keys remain on Crave-managed services. Never collect or log raw card numbers. After Stripe returns, poll `storefront.checkout.getOrderResult(locationId, cartId)`. Treat only `completed` as success; `payment_pending` and `order_pending` are retryable states, while `failed` is terminal. See the complete checkout state machine. Poll the order result and load protected receipts. # Prebuilt Storefront Prompt Source: https://docs.craveup.com/guides/ai/build-storefront-prompt Give an AI coding agent the direct public Storefront API security and delivery contract. Copy the prompt below into your coding agent's project instructions. ```text theme={null} Build a production-ready Crave restaurant storefront. Use the exact @craveup/storefront-sdk@2.0.1 package with: const crave = createStorefrontClient({ baseUrl: process.env.NEXT_PUBLIC_CRAVEUP_API_URL!, sessionStore, getAuthToken, }); Requirements: - Published catalog reads are anonymous; never add a browser API key. - Use versioned sessionStorage scoped by canonical API environment, merchant, and location for cart capability and revision state. - Keep cart capabilities, customer JWTs, receipt tokens, and PaymentIntent client secrets out of URLs, logs, analytics, and persistent storage. - Start ordering sessions with idempotency, then use SDK cart methods so capability, ETag, If-Match, and idempotency headers remain consistent. - On CART_CONFLICT, reload current cart state before presenting a retry. - Include merchantSlug in customer login and OTP requests. - Confirm payments with Stripe.js and poll all four order-result states: payment_pending, order_pending, completed, failed. Never assume success. - Capture receipt capabilities from URL fragments, remove the fragment immediately, and keep each capability only in tab-scoped storage scoped by API environment, merchant, and receipt ID. - Keep Stripe secret keys, POS credentials, loyalty credentials, webhook secrets, and private Crave integration keys on the API service. - Use exact CORS origins for each deployed storefront. Before completion, run tests, lint, type checking, a production build, a browser-bundle credential scan, and a sandbox end-to-end ordering check. Report exact commands and remaining deployment requirements. ``` Useful references: * [Storefront SDK](/getting-started/storefront-sdk) * [API overview](/api-reference/overview/api-overview) * [Build a Custom Storefront](/guides/build-custom-storefront) * [Deployment](/guides/deployment) # Use the CraveUp MCP Server Source: https://docs.craveup.com/guides/ai/craveup-mcp-server Connect AI agents to CraveUp tools for storefront setup, menu import, readiness checks, and go-live onboarding. The CraveUp MCP server is an agent-facing developer tool. It can create or update restaurant setup data when authenticated, so connect it only in workspaces where the agent is allowed to make those changes. CraveUp provides a Model Context Protocol server through the `@craveup/mcp` package. It lets AI agents use Crave-specific tools instead of guessing API shapes from memory. Use it when you want an agent to: * inspect or import menus * connect a storefront project to a Crave location * check storefront readiness before launch * configure location ordering settings * work through go-live onboarding steps * fetch Crave implementation guidance while building a storefront ## Setup The MCP server runs over stdio. Use the public npm package for normal usage outside this monorepo, or point your client at the local repo build when testing unreleased tools. You do not need a clone of `craveup-turborepo` to use the MCP server — `npx -y @craveup/mcp` fetches everything it needs. Most configurations below set `CRAVEUP_API_KEY` because menu imports, edits, and private readiness checks need a trusted server credential. Public storefront guides, project wiring, and published menu reads do not use that key. If you do not have an organization yet, register the server with no credentials and sign in from chat instead — see [Sign in from chat](#sign-in-from-chat). ### Cursor Add this to your Cursor MCP configuration: ```json theme={null} { "mcpServers": { "craveup": { "command": "npx", "args": ["-y", "@craveup/mcp"], "env": { "CRAVEUP_ENVIRONMENT": "sandbox", "CRAVEUP_API_KEY": "crv_test_...", "CRAVEUP_LOCATION_ID": "loc_..." } } } } ``` Restart Cursor or reload the window after changing the MCP configuration. Inside `craveup-turborepo`, pnpm workspace resolution can shadow the published package. If you are working in this repo, use the local repo build configuration below. ### Codex Add separate sandbox and production servers from the terminal: ```bash theme={null} codex mcp add craveup-sandbox \ --env CRAVEUP_ENVIRONMENT=sandbox \ --env CRAVEUP_API_KEY=crv_test_... \ --env CRAVEUP_LOCATION_ID=loc_... \ -- npx -y @craveup/mcp codex mcp add craveup-production \ --env CRAVEUP_ENVIRONMENT=production \ -- npx -y @craveup/mcp ``` If your Codex configuration uses TOML, add: ```toml theme={null} [mcp_servers.craveup] command = "npx" args = ["-y", "@craveup/mcp"] [mcp_servers.craveup.env] CRAVEUP_ENVIRONMENT = "sandbox" CRAVEUP_API_KEY = "crv_test_..." CRAVEUP_LOCATION_ID = "loc_..." ``` ### Claude Code CraveUp is a local stdio MCP server, not a hosted HTTP endpoint. Register separate sandbox and production processes at user scope: ```bash theme={null} claude mcp add -s user craveup-sandbox \ -e CRAVEUP_ENVIRONMENT=sandbox \ -- npx -y @craveup/mcp claude mcp add -s user craveup-production \ -e CRAVEUP_ENVIRONMENT=production \ -- npx -y @craveup/mcp ``` Run `/mcp` in Claude Code to inspect or reconnect the server. Start a new Claude Code session if the current session does not refresh its tool inventory after registration. Use `-s project` only when you intentionally want to commit a team-wide `.mcp.json`. Never place API keys or access tokens in that file. ### Claude Desktop Add this to `~/Library/Application Support/Claude/claude_desktop_config.json`: ```json theme={null} { "mcpServers": { "craveup": { "command": "npx", "args": ["-y", "@craveup/mcp"], "env": { "CRAVEUP_ENVIRONMENT": "sandbox", "CRAVEUP_API_KEY": "crv_test_...", "CRAVEUP_LOCATION_ID": "loc_..." } } } } ``` Restart Claude Desktop after saving the file. ### Local repo build Use this when you are working inside `craveup-turborepo` and need the newest MCP tools from source: ```bash theme={null} pnpm --filter @craveup/mcp build ``` Then configure your MCP client to run the built CLI: ```json theme={null} { "mcpServers": { "craveup": { "command": "node", "args": ["/absolute/path/to/craveup-turborepo/packages/mcp/dist/cli.js"], "env": { "CRAVEUP_ENVIRONMENT": "sandbox", "CRAVEUP_API_KEY": "crv_test_...", "CRAVEUP_LOCATION_ID": "loc_..." } } } } ``` ## Environment and authentication `CRAVEUP_ENVIRONMENT` is required and must be `sandbox` or `production`. The MCP server resolves the dashboard, public/admin APIs, Clerk issuer/client, expected API-key prefix, and credential namespace from that one value. It refuses a test/live key or endpoint mismatch before making a request. The environment is immutable for the lifetime of an MCP process. Use two named registrations rather than an in-session switch: `craveup-sandbox` for development and `craveup-production` for go-live work. This keeps hosts, credentials, API keys, and browser sessions isolated. | Environment | Dashboard | API key prefix | | - | - | - | | `sandbox` | `sandbox.dashboard.craveup.com` | `crv_test_...` | | `production` | `dashboard.craveup.com` | `crv_live_...` | Sandbox remains isolated from live data and can be used for storefront setup, catalog, and cart development. During the current direct-charge cutover, ordinary Stripe payment admission is deliberately hard locked in both sandbox and production: the API returns `STRIPE_DIRECT_CHARGE_CUTOVER_INCOMPLETE`. Present checkout as temporarily unavailable and do not retry or bypass that response. Dashboard, MCP, and setup tools remain available while the payment cutover is completed. The MCP server supports three authentication modes. | Mode | Use it for | Required setup | | - | - | - | | **Crave API key** | Menu import, product edits, integration status, and the private part of readiness checks | Set `CRAVEUP_API_KEY`, and usually `CRAVEUP_LOCATION_ID` | | **Public Storefront API** | Published menu reads, direct-integration guides, and project environment wiring | No credential; provide a published `locationId` when the tool needs one | | **CraveUp login session** | Organization setup, project wiring, go-live onboarding, and dashboard-backed operations | Ask the agent to sign you in (see below), run `craveup login`, or set `CRAVEUP_ACCESS_TOKEN` | | **Expo robot token** | Dispatch and monitor approved internal mobile release workflows | Set `EXPO_TOKEN` in the MCP server environment; never put it in prompt text | To create an API key, open Business Manager and go to [Dashboard > Developers > API Development > Step 1 - API Keys](/guides/generate-api-key). API keys are tied to an organization and may be restricted to specific locations; each MCP call still needs a target `locationId`. ### Sign in from chat Requires `@craveup/mcp` 0.5.0 or later. An API key is scoped to an organization that already exists, so it cannot unlock organization setup or the go-live tools. For those, the agent can start a browser sign-in for you — no `craveup login` in a terminal, and no API key in the configuration. `CRAVEUP_ENVIRONMENT` is still required, since it selects which dashboard and Clerk issuer the sign-in targets: ```json theme={null} { "mcpServers": { "craveup": { "command": "npx", "args": ["-y", "@craveup/mcp"], "env": { "CRAVEUP_ENVIRONMENT": "sandbox" } } } } ``` Then ask the agent to sign you in. It runs: 1. `start_crave_auth` — returns a hosted Crave Up URL. Open it and sign in, or create a free account. 2. `complete_crave_auth` — finishes the exchange and stores the session. The session is written to the macOS keychain, falling back to `~/.config/craveup/credentials.json`, and is shared with the `craveup` CLI — so signing in through either one satisfies the other. Run `whoami` at any time to confirm which account is active. On versions before 0.5.0 these tools are absent. Upgrade, or run `craveup login` from a terminal to produce the same shared session. Normal MCP requests reuse the persisted CLI/MCP session; they do not open a browser. Browser authentication happens only when `start_crave_auth` (or `craveup login`) is explicitly started. Sandbox and production sessions are stored separately. Do not put production API keys in shared prompt text, screenshots, recordings, or public repository files. Pass credentials through the MCP client environment instead. ## Available tools Exact tool availability depends on the installed package version. The current repository source exposes these tool groups; installed npm versions may differ: | Tool group | Examples | Purpose | | - | - | - | | **Onboarding** | `get_onboarding_flow`, `whoami`, `create_organization`, `connect_project` | Move a restaurant project from first setup to a connected Crave storefront | | **Browser auth** | `start_crave_auth`, `complete_crave_auth`, `cancel_crave_auth` | Sign in or sign up from chat without leaving the conversation (0.5.0 and later) | | **Menu and storefront** | `import_menu`, `list_menus`, `get_product`, `update_product`, `set_product_availability`, `delete_product`, `check_storefront_readiness` | Import, inspect, edit, and verify storefront data | | **Location settings** | `get_location_settings`, `update_store_hours`, `set_takeout_enabled`, `set_scheduled_orders_enabled`, `open_location_for_orders` | Fix ordering gates such as closed-store or unavailable pickup states | | **Discounts** | `list_discounts`, `create_order_discount` | Read location promo codes and create order-level discounts idempotently | | **Go-live** | `get_onboarding_status`, `set_location_address`, `set_fulfillment_methods`, `list_subscription_plans`, `start_subscription_checkout` | Continue setup at any time; subscription and Connect are required only for production payments | | **Guidance** | `get_integration_guide` | Ask for Crave-specific implementation guidance while coding | | **Runtime status** | `get_mcp_capabilities` | Inspect versions, policy digest, and fail-closed compatibility blockers without credentials | | **Mobile releases** | `check_mobile_release_readiness`, `start_mobile_release`, `get_mobile_release_status` | Preflight internal releases and dispatch TestFlight with explicit approval | Multi-location and sandbox tools — `create_location`, `delete_location`, `list_locations`, `get_location_orders`, `list_customers`, `set_menu_active`, and `seed_sandbox_analytics` — are available from 0.4.0. Browser auth is available from 0.5.0. The repo source can carry tools ahead of the published package; use the local repo build if you need one before its release. All current tools return one versioned operation envelope in both text and `structuredContent`. Review-required writes preview without side effects and require the exact returned intent digest for apply. Destructive, paid, and release operations require a separate confirmation phrase. Use `get_mcp_capabilities` to inspect the policy digest and current blockers. A ready intent digest does not claim that target pre-state, project registry evidence, bounded background jobs, or opaque cursor pagination exist; those capabilities remain fail-closed until their server/release contracts are published. Policies whose inventory says `idempotencyStrategy: unverified` remain blocked after preview. Today that includes `create_organization`, `create_location`, `start_subscription_checkout`, and `start_mobile_release`. Complete those actions in Business Manager or the provider workflow until the authoritative service provides a durable operation identity and response-loss reconciliation. `check_storefront_readiness` reads the published menu and the side-effect-free `GET /ordering-readiness` endpoint without creating a cart, then reads private integration status for payment configuration. The private step requires `CRAVEUP_API_KEY`. A ready payment check describes configuration only; the tool does not create a PaymentIntent, prove payment admission, or execute a test payment. While the direct-charge lock exists, checkout still returns `STRIPE_DIRECT_CHARGE_CUTOVER_INCOMPLETE`. For mobile releases, first run `craveup apps configure` in the Expo/React Native project and commit the generated `.eas/workflows` files. The MCP release trigger only supports internal TestFlight. It requires the exact confirmation phrase and a scoped Expo robot token because dispatching a workflow consumes EAS build minutes. Android internal publishing stays in the CLI so the local Play submit profile can be verified immediately before submission. Apple password/2FA prompts remain in a visible terminal, and public App Store review remains a separate App Store Connect action. ## Example prompts Once connected, ask your agent for Crave-specific work: ```text theme={null} Use the CraveUp MCP server to sign me in to Crave Up, then tell me which account and organization I am on. ``` ```text theme={null} Use the CraveUp MCP server to inspect my current location, list active menus, and tell me whether this storefront can accept orders. ``` ```text theme={null} Use the CraveUp MCP server and the Crave docs to build a Next.js menu page with category tabs, cart state, and the server-selected checkout payment-session flow. If payment admission returns STRIPE_DIRECT_CHARGE_CUTOVER_INCOMPLETE, show checkout as temporarily unavailable without retrying. ``` ```text theme={null} Import this menu into Crave. Preserve categories, products, prices, modifier groups, availability, and image URLs. After import, verify the active menu with list_menus. ``` ## Faster setup with a prebuilt prompt Use the prebuilt prompt when you want an AI agent to start with Crave Cloud, Crave.js, Storefront API, and MCP context already loaded. Copy a ready-to-use agent prompt for building a Crave-powered storefront. Create a server-only key only when private MCP tools need it. A storefront never receives that key. ## Troubleshooting | Problem | Fix | | - | - | | The agent cannot see CraveUp tools | Restart the MCP client after changing configuration. In Cursor, reload the window. | | `CRAVEUP_ENVIRONMENT is required` | Set it to `sandbox` or `production` in the MCP server environment. | | `No Crave session` appears | Call `start_crave_auth` then `complete_crave_auth`, run `craveup login`, or set `CRAVEUP_ACCESS_TOKEN`. | | Storefront tools fail with an auth error | Confirm the key prefix matches the selected environment and can access the target location. | | Stripe checkout reports `STRIPE_DIRECT_CHARGE_CUTOVER_INCOMPLETE` | The direct-charge activation lock is working as designed. Keep setup/catalog/cart work available, show checkout as temporarily unavailable, and do not retry or bypass it. | | Sandbox asks for Stripe Connect setup | Do not bypass the returned setup state. During the cutover, continue non-payment work; after activation, follow the current onboarding status for that location. | | Menu tools use the wrong location | Set `CRAVEUP_LOCATION_ID`, or pass `locationId` explicitly in the tool call. | | New tools do not appear after a repo update | Rebuild `@craveup/mcp`, then restart the MCP client. | # Analytics Source: https://docs.craveup.com/guides/analytics Track storefront events and measure ordering funnel performance. The Crave SDK includes a built-in analytics module that tracks key events across the ordering funnel. Use it to measure conversion rates, identify drop-off points, and understand customer behavior. ## Event types The public SDK accepts three cart-authorized interaction events. Crave records `ORDER_PLACED` server-side only after payment succeeds and the order is created, so clients cannot forge a conversion. | Event | When to fire | Funnel stage | | - | - | - | | `SCAN` | Customer scans a QR code or visits the storefront URL | Entry | | `CART_VIEW` | Customer opens the cart panel | Consideration | | `CHECKOUT_VIEW` | Customer reaches the checkout page | Intent | | `ORDER_PLACED` | Recorded automatically by Crave after order creation | Conversion | ## Track events with the SDK ```ts SDK theme={null} import { storefront } from '@/lib/storefront'; // Track a storefront visit await storefront.analyticsEvents.track(locationId, { cartId, eventType: 'SCAN', }); // Track cart view with metadata await storefront.analyticsEvents.track(locationId, { cartId, eventType: 'CART_VIEW', metadata: { itemCount: cart.totalQuantity, total: cart.orderTotalWithServiceFee, }, }); // Track checkout view await storefront.analyticsEvents.track(locationId, { cartId, eventType: 'CHECKOUT_VIEW', metadata: { fulfillmentMethod: cart.fulfilmentMethod, }, }); ``` ```bash REST theme={null} curl -X POST "https://api.craveup.com/api/v1/storefront/locations/loc_123/analytics-events" \ -H "Content-Type: application/json" \ -H "X-Cart-Token: CART_CAPABILITY" \ -H "Idempotency-Key: analytics_01" \ -d '{ "cartId": "cart_456", "eventType": "CHECKOUT_VIEW" }' ``` The API returns `{ "status": "accepted" }` after it authorizes the cart and records or deduplicates the event. ## Where to fire events Place analytics calls at these points in your storefront: ```tsx filename="src/app/page.tsx" theme={null} // SCAN — fire after the ordering session creates or restores the cart useEffect(() => { if (cartId) { storefront.analyticsEvents.track(locationId, { cartId, eventType: "SCAN" }); } }, [cartId]); ``` ```tsx filename="src/components/CartPanel.tsx" theme={null} // CART_VIEW — fire when cart panel opens function CartPanel({ isOpen }: { isOpen: boolean }) { useEffect(() => { if (isOpen && cartId) { storefront.analyticsEvents.track(locationId, { cartId, eventType: "CART_VIEW", }); } }, [isOpen]); // ... } ``` ```tsx filename="src/app/checkout/page.tsx" theme={null} // CHECKOUT_VIEW — fire on checkout page mount useEffect(() => { if (cartId) { storefront.analyticsEvents.track(locationId, { cartId, eventType: "CHECKOUT_VIEW", }); } }, [cartId]); ``` Do not send `ORDER_PLACED` from a browser or mobile client. Poll the authoritative order result for the UI; the API writes the conversion event during server-side order creation. ## Custom metadata The `metadata` field accepts any key-value pairs. Use it to attach context to events: ```ts theme={null} await storefront.analyticsEvents.track(locationId, { cartId, eventType: "CART_VIEW", metadata: { source: "qr_code", campaignId: "summer_promo_2025", deviceType: "mobile", itemCount: cart.totalQuantity, total: cart.orderTotalWithServiceFee, }, }); ``` ## Google Analytics integration Forward Crave events to Google Analytics for unified reporting: ```ts filename="src/lib/analytics.ts" theme={null} import { storefront } from "@/lib/storefront"; type PublicFunnelEvent = "SCAN" | "CART_VIEW" | "CHECKOUT_VIEW"; const GA_EVENT_MAP: Record = { SCAN: "storefront_visit", CART_VIEW: "view_cart", CHECKOUT_VIEW: "begin_checkout", }; export async function trackEvent( locationId: string, eventType: PublicFunnelEvent, cartId: string, metadata?: Record, ) { // Send to Crave await storefront.analyticsEvents.track(locationId, { cartId, eventType, metadata, }); // Forward to Google Analytics if (typeof window !== "undefined" && window.gtag) { window.gtag("event", GA_EVENT_MAP[eventType], { currency: metadata?.currency ?? "USD", value: metadata?.total ?? 0, items: metadata?.items ?? [], }); } } ``` If you also send a Google Analytics purchase event, do so only after `checkout.getOrderResult()` returns `completed`; do not forward that event to the Crave public analytics endpoint. ## View analytics in the Dashboard Analytics data is available in the Crave Dashboard under **Analytics > Storefront**. The dashboard shows: * **Funnel visualization** — conversion rates between each stage * **Event timeline** — raw event stream with metadata * **Location comparison** — performance across multiple locations * **Time-based trends** — daily, weekly, and monthly patterns ## Best practices * **Fire events once** — deduplicate using a ref or flag to prevent double-counting on re-renders * **Don't block the UI** — fire analytics calls without `await` to keep the interface responsive * **Include metadata** — the more context you attach, the more useful your Dashboard reports * **Test outside production first** — confirm events appear in the intended Dashboard environment ```ts theme={null} // Fire-and-forget pattern (don't await) storefront.analyticsEvents .track(locationId, { cartId, eventType: "CART_VIEW", }) .catch(console.error); ``` ## Next steps Track order status after checkout. Deploy your storefront and go live. # Build a Custom Storefront Source: https://docs.craveup.com/guides/build-custom-storefront Scaffold and extend a direct, capability-safe Crave ordering experience. ## 1. Scaffold with the CLI ```bash theme={null} npx @craveup/cli init ``` The CLI may authenticate you to discover or validate merchant resources, but it does not write a private credential into the generated browser app. ## 2. Configure public values ```env theme={null} NEXT_PUBLIC_CRAVEUP_API_URL=https://api.craveup.com NEXT_PUBLIC_CRAVEUP_LOCATION_ID=loc_456def NEXT_PUBLIC_CRAVEUP_MERCHANT_SLUG=downtown-pizza NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=pk_live_xxx ``` The API origin is configuration, not a credential. Use the sandbox origin and publishable key in non-production environments. These merchant and location variables are standalone/local overrides. The hosted template resolves the merchant from its controlled host/subdomain and the location from the `[locationId]` route. ## 3. Create separate browser and server clients Browser code supplies a tab-scoped cart `sessionStore` and `getAuthToken`. Server Components create a client with the same explicit `baseUrl` and perform anonymous published reads only. ```ts theme={null} import { createStorefrontClient } from "@craveup/storefront-sdk"; export const storefront = createStorefrontClient({ baseUrl: process.env.NEXT_PUBLIC_CRAVEUP_API_URL!, sessionStore, getAuthToken, }); ``` ## 4. Follow the capability lifecycle 1. Load published merchant, location, and menu data anonymously. 2. Start an ordering session with a generated idempotency key. 3. Store the returned cart capability in versioned `sessionStorage`, scoped by canonical API environment, merchant, and location. 4. Let the SDK attach the capability, `If-Match`, and idempotency headers. 5. On `CART_CONFLICT`, reload the cart before asking the shopper to retry. 6. Clear the capability after claim, deletion, expiry, or terminal checkout handling. ## 5. Checkout ```ts theme={null} const payment = await storefront.checkout.createPaymentSession( locationId, cartId, { includeCustomerContext: true }, ); ``` Use `includeCustomerContext` only when a signed-in shopper still has a guest capability and the selected provider needs customer association. Omit it for guest checkout; a claimed cart uses customer authentication automatically. Render Square Web Payments or Stripe Elements based on `payment.provider`, then poll `checkout.getOrderResult`. Treat `payment_pending` and `order_pending` as nonterminal; render success only for `completed`; render an actionable failure for `failed`. ## 6. Customer and receipt access Login and OTP requests must include `merchantSlug`. Customer resources use the returned JWT, stored by API environment and merchant. Receipt links use a purpose-limited fragment capability: remove the fragment immediately, keep the token in tab-scoped storage keyed by API environment, merchant, and receipt ID, and pass it through `receipts.get`. ## 7. Deploy * Configure the exact storefront origin in the API CORS allowlist. * Keep provider and private integration credentials on the API service. * Run tests, lint, type checking, a production build, and a static-bundle secret scan. * Verify a real published catalog read and a sandbox ordering flow before promoting production. See [Deployment](/guides/deployment) and the [Storefront SDK](/getting-started/storefront-sdk) for the full contract. # Checkout Flow Source: https://docs.craveup.com/guides/checkout-flow Finalize a capability-protected cart, confirm payment, and poll the authoritative order result. Checkout uses the same cart capability, revision, and idempotency protections as other cart mutations. Creating a payment intent is a write and is never an anonymous lookup by cart ID. ## 1. Finalize the cart ```ts theme={null} await storefront.cart.update("loc_123", cartId, { fulfillmentMethod: "takeout", }); await storefront.cart.updateOrderTime("loc_123", cartId, { pickupType: "ASAP", }); await storefront.cart.validateAndUpdateCustomer("loc_123", cartId, { customerName: "Alex Johnson", emailAddress: "alex@example.com", phoneNumber: "+1234567890", }); ``` Each call uses the latest cart revision. If a call returns `CART_CONFLICT`, refresh the cart and require an explicit retry. ## 2. Create the payment session ```ts theme={null} const payment = await storefront.checkout.createPaymentSession( "loc_123", cartId, { idempotencyKey: crypto.randomUUID() }, ); ``` ```bash theme={null} curl -X POST "https://api.craveup.com/api/v1/storefront/locations/loc_123/carts/cart_456/payment-session" \ -H "X-Cart-Token: $CART_ACCESS_TOKEN" \ -H 'If-Match: "cart-4"' \ -H "Idempotency-Key: payment_01" ``` Branch on `payment.provider`. Pass a Stripe `clientSecret` to Stripe Elements, or initialize Square Web Payments with its application and location IDs. Raw card data and provider secrets never pass through storefront code. ## 3. Confirm payment ```tsx theme={null} const { error } = await stripe.confirmPayment({ elements, confirmParams: { return_url: `${window.location.origin}/order/confirmation`, }, }); ``` Do not add the cart ID or capability to `return_url`. Keep the cart session in tab-scoped storage so the returning page can continue securely. ## 4. Poll the order result ```ts theme={null} const result = await storefront.checkout.getOrderResult("loc_123", cartId); switch (result.state) { case "payment_pending": case "order_pending": // Poll again with bounded backoff. break; case "completed": // Render result.order, then clear persisted cart capability state. break; case "failed": // Show a retry-safe failure using result.code, then clear terminal state. break; } ``` Only `completed` is success. Never infer success from a redirect or from the absence of an error. Integrate Stripe Elements without exposing server secrets. Implement bounded result polling and receipts. # Crave Cloud Overview Source: https://docs.craveup.com/guides/crave-cloud-overview How Crave Cloud connects franchise ordering, identity, loyalty, payments, operations, APIs, and open developer surfaces. Crave Cloud is the franchise platform behind Crave.js. It gives restaurant brands one integration surface for digital ordering while letting each module be adopted independently, connected to systems the brand already trusts, or kept on the roadmap until the brand is ready. For developers, that means Crave.js is not a standalone demo app. It is the storefront layer inside a broader cloud platform for restaurants with multiple locations, operators, payment accounts, loyalty providers, and guest touchpoints. This page separates shipped surfaces from target product direction. Named bring-your-own providers are integration boundaries, not guaranteed prebuilt connectors. ## The platform model Guests, teams, apps, kiosks, franchisees, operators, developers, and restaurant locations. Business Manager foundations, Crave.js, API contracts, provider integration health, readiness, and module configuration. One customer experience across ordering, loyalty, payments, locations, and brand operations as each module is enabled. ## Ecosystem map Read Crave Cloud left to right: your guest and operator channels connect once to Crave Cloud, Crave Cloud coordinates the modules, and the brand gets one connected franchise experience. Status labels show what is shipped today versus target direction. ```mermaid theme={null} flowchart LR subgraph World["Your world"] Guests["Guests
Order, pay, earn, redeem when loyalty is connected"] Teams["Your teams
HQ, franchisees, operators, developers"] Channels["Digital channels
Mobile app, web ordering, kiosk, custom storefronts"] Network["Restaurant network
One brand, 1 to 500+ locations"] Guests --> Channels Teams --> Network end subgraph Cloud["Crave Cloud"] Connection["One connection
Choose modules independently"] Manager["Business Manager
Orgs, locations, roles, readiness, integrations
Shipped foundations"] Surface["Crave.js surface
SDK, REST, CLI, UI components
Shipped"] subgraph Modules["Choose modules independently"] Identity["Customer identity
Target managed identity or BYO"] Ordering["Ordering
Crave.js shipped"] Loyalty["Loyalty
Engine exists; Cloud integration work"] Payments["Payments
Contract implemented; admission locked"] end Connection --> Surface Manager --> Surface Surface --> Identity Identity --> Ordering Identity --> Loyalty Ordering --> Payments Ordering --> Loyalty end subgraph Result["Connected franchise experience"] Customer["One customer across the brand
Target"] DigitalOrdering["Flexible digital ordering
Shipped for Crave.js path"] Rewards["Brand-wide loyalty
Target"] Routing["Flexible payment routing
Stripe contract"] Ops["Centralized operations
Foundations"] Keep["Keep what works
Design"] end Channels --> Surface Network --> Surface Surface --> Customer Surface --> DigitalOrdering Loyalty --> Rewards Payments --> Routing Manager --> Ops Modules --> Keep ``` This map is intentionally simplified from the internal architecture diagram. Bring-your-own vendors are integration boundaries, not prebuilt connector promises. Confirm enablement per brand. ## Core surfaces | Surface | What it does | | - | - | | **Business Manager** | Provides shipped foundations for organizations, locations, roles, readiness, integrations, analytics, and operational configuration. | | **Crave.js** | Provides the shipped storefront developer surface: SDK, REST API patterns, CLI, and React components for branded ordering experiences. | | **APIs** | Give digital channels one integration surface for menus, carts, checkout, fulfillment, the Stripe payment contract, and analytics. Direct-charge admission remains locked. | | **Modules** | Let brands choose ordering, identity, loyalty, and payments independently. Some modules are shipped, some are target direction, and some require brand-specific integration work. | ## Choose modules independently Crave Cloud is designed for gradual adoption. A brand can use Crave.js ordering while keeping an existing loyalty provider, or prepare the Stripe payment contract while connecting an existing ordering platform. The goal is to replace only what needs replacing; payment admission remains unavailable until the direct-charge cutover is activated. For loyalty, **Loyalty Interchange Protocol** means Crave's standard contract for balances, rewards, tiers, earning, redemption, refunds, and reversals across channels. The engine and contract exist; the Cloud-to-ordering integration layer is the product glue that makes it usable inside Crave Cloud. | Module | Current or managed path | Bring-your-own path | Status | | - | - | - | - | | **Customer identity** | Target Clerk-backed Crave Customer Identity. The storefront today still uses Stytch OTP. | Existing identity provider using Auth0, Okta, Entra, Clerk, or OIDC patterns. | Target + BYO | | **Ordering** | Crave.js menus, carts, checkout, fulfillment, analytics, Storefront SDK, REST API, and components. | Existing ordering platform, OMS, or custom storefront integration. | Crave.js shipped | | **Loyalty** | Loyalty Interchange Protocol contract and engine exist; Cloud-to-ordering integration work connects it to ordering flows. | Existing loyalty provider such as Punchh, Paytronix, Thanx, or Spendgo through integration work. | Engine + integrate | | **Payments** | Server-selected checkout sessions; Stripe direct-charge payment admission is cutover locked until activation. | Existing gateway or processor integration such as Adyen, Braintree, Worldpay, or a custom gateway. | Direct-charge cutover locked | Bring-your-own providers are integration boundaries, not blanket availability promises. Use the current implementation guides and your Crave integration plan to confirm what is enabled for a specific brand. ## Identity boundary Crave Cloud keeps **guest identity** and **staff identity** separate. * Guest identity powers storefront ordering, loyalty, rewards, and customer-facing channels. * Staff identity powers Business Manager access, roles, franchise operations, and administrative workflows. * Guests never become Business Manager organization members. * Crave does not store guest passwords or issue guest auth tokens. * Merchant keys and guest JWTs are not sent to Loyalty Interchange Protocol endpoints. This separation matters for franchise brands because a customer can order across locations without becoming a staff user, and a franchise operator can manage locations without gaining access to guest accounts. The target managed identity path is Clerk-backed Crave Customer Identity. The current storefront implementation still uses Stytch OTP, and bring-your-own identity providers are handled through brand-specific integration planning. ## Where Crave.js fits Crave.js is the developer-facing ordering layer inside Crave Cloud: * install the exact `@craveup/storefront-sdk@2.0.1` release * scaffold storefronts with `npx @craveup/cli init` * connect menus, modifiers, carts, checkout, fulfillment, the activation-locked Stripe payment contract, and analytics * use typed SDK helpers or direct REST calls * customize UI components while keeping the restaurant ordering workflow intact Crave.js is not the staff dashboard, the loyalty runtime, or a POS replacement. Those belong to other Crave Cloud surfaces or brand-specific integration work. Start with [Build a Restaurant Ordering App in Minutes](/getting-started/introduction), then use this overview to understand how that storefront fits into Crave Cloud. # Deployment Source: https://docs.craveup.com/guides/deployment Deploy a direct Crave storefront with exact origins and no browser credentials. ## Storefront environment | Variable | Required | Description | | - | - | - | | `NEXT_PUBLIC_CRAVEUP_API_URL` | Yes | Public Crave API origin. | | `NEXT_PUBLIC_CRAVEUP_LOCATION_ID` | Standalone/local quickstarts | Published location override. | | `NEXT_PUBLIC_CRAVEUP_MERCHANT_SLUG` | Standalone/local quickstarts | Published merchant override. | | `NEXT_PUBLIC_STOREFRONT_URL` | Optional | Canonical metadata override; defaults to the request host. | | `NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY` | For checkout | Browser-safe Stripe publishable key for the same environment. | | `NEXT_PUBLIC_GOOGLE_MAPS_API_KEY` | For delivery maps | Browser key restricted by domain and API. | In the hosted `cravejs-storefront-template`, the controlled host/subdomain selects the merchant and the `[locationId]` route selects the location. Do not duplicate those tenant selectors as production environment variables. The prefixed merchant and location variables above are only for standalone or local quickstart wiring. The canonical storefront origin is host-derived from request headers by default; set `NEXT_PUBLIC_STOREFRONT_URL` only when deployment metadata needs an explicit override. Do not configure private Crave API keys, provider credentials or inbound webhook secrets, cart capabilities, customer JWTs, or receipt capabilities as public variables. ## API service environment Provider credentials remain on the API service. Every browser deployment needs its exact browser origin registered before it sends credentials or capabilities. An origin is the scheme, hostname, and non-default port; paths are not part of an origin. For the Crave-managed API, provide the local-development, stable-preview, and production origins to the Crave integration owner. There is currently no wildcard or arbitrary self-service bypass. Register stable preview origins individually; do not attempt to allow every generated preview hostname. Operators of the API service configure the approved origins as a comma-separated list: ```env theme={null} CORS_ALLOWED_ORIGINS=https://order.example.com,https://preview.example.com ``` First-party `https://*.craveup.com` origins are allowed by default. Localhost is allowed only outside production. The API accepts `Authorization`, `X-Cart-Token`, `X-Checkout-Handoff`, `X-Receipt-Token`, `Idempotency-Key`, `If-Match`, `X-Request-Id`, and `Content-Type`, and exposes `ETag` plus `X-Request-Id`. Restart or redeploy the API after changing its static allowlist, then verify the preflight from the exact deployed origin. Native iOS and Android requests normally omit the browser `Origin` header. Expo web, embedded web views that send an origin, and browser storefronts still require exact registration. ## Vercel or Netlify 1. Import the storefront repository. 2. Add the public variables for the correct environment. 3. Add the deployed origin to the API allowlist before testing cross-origin requests. 4. Build and deploy a preview. 5. Verify anonymous catalog reads, ordering-session creation, cart mutation, PaymentIntent creation, and terminal order polling. 6. Promote the exact verified commit to production. ## Required release gates ```bash theme={null} pnpm test pnpm lint pnpm check-types pnpm build ``` Also scan generated browser assets for credentials and validate that cart/customer/receipt capabilities never appear in URLs or analytics. A healthy page load alone is not an ordering-flow verification. ## Rotation and rollback Rotating private provider credentials does not require rebuilding the storefront. Rotating the cart capability key ring requires the API migration/runbook for active carts. Roll back API and storefront releases together only when their public contract differs; otherwise deploy the failing surface independently. # Display Menu Source: https://docs.craveup.com/guides/display-menu Fetch menus, categories, and products for a location. This guide covers how to load a restaurant's menu data and display it in your storefront — including multiple menus, category filtering, and product details. ## How menus work Every Crave location has one or more **menus**. Each menu contains **categories**, and each category links to **products**. The active menu depends on the current time and the location's schedule. ``` Merchant → Location → Menu(s) → Categories → Products → Modifiers ``` ## Fetch location data The location response includes basic metadata. Menu and product data comes from the ordering session and API endpoints. ```ts SDK theme={null} import { storefront } from '@/lib/storefront'; const location = await storefront.locations.getById('loc_123'); console.log(location.restaurantDisplayName); ``` ```bash REST theme={null} curl "https://api.craveup.com/api/v1/storefront/locations/loc_123" ``` ## Start a session to get the menu Menus are anonymous published resources. Start an ordering session only when the customer begins a cart; the response includes the cart capability used for later cart operations. ```ts SDK theme={null} const session = await storefront.orderingSessions.start('loc_123', { marketplaceId: 'web', fulfillmentMethod: 'takeout', }); const menu = await storefront.menus.list('loc_123', {}); ``` ```bash REST theme={null} curl -X POST "https://api.craveup.com/api/v1/storefront/locations/loc_123/ordering-sessions" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: session_01" \ -d '{"marketplaceId":"web","fulfillmentMethod":"takeout"}' ``` `marketplaceId` is optional source attribution, such as `"web"`; it is not the location identifier. The route already provides the location scope. ## Display categories Categories are used to group products (e.g., "Appetizers", "Mains", "Drinks"). Render them as tabs or a sidebar. ```tsx filename="components/CategoryTabs.tsx" theme={null} interface Category { id: string; name: string; productIds: string[]; } function CategoryTabs({ categories, activeId, onSelect, }: { categories: Category[]; activeId: string; onSelect: (id: string) => void; }) { return (
{categories.map((cat) => ( ))}
); } ``` ## Display products Each product includes a name, description, price, images, and modifier groups. ```tsx filename="components/ProductCard.tsx" theme={null} interface Product { id: string; name: string; description: string; price: string; displayPrice: string; images: string[]; } function ProductCard({ product, onAdd, }: { product: Product; onAdd: () => void; }) { return (
{product.images[0] && ( {product.name} )}

{product.name}

{product.description}

{product.displayPrice}
); } ``` ## Filter and search Crave menus are returned as structured data. You can implement client-side filtering: ```ts theme={null} // Filter products by category const categoryProducts = products.filter((p) => activeCategory.productIds.includes(p.id) ); // Search by name const searchResults = products.filter((p) => p.name.toLowerCase().includes(query.toLowerCase()) ``` ## Get order times Fetch the available time slots for ASAP or scheduled orders. ```ts SDK theme={null} const orderTimes = await storefront.locations.getOrderTimes('loc_123'); if (orderTimes.requireScheduledOrders) { // Show day/time picker from orderTimes.orderDays } else { // ASAP is available; optionally show schedule option console.log('Schedule allowed:', orderTimes.scheduleAllowed); } ``` ```bash REST theme={null} curl "https://api.craveup.com/api/v1/storefront/locations/loc_123/time-intervals" ``` ## Product types The SDK exports these types for working with menu data: | Type | Description | | - | - | | `Menu` | Top-level menu with `id`, `name`, `isActive`, and `categories` | | `Category` | Grouping with `id`, `name`, and `productIds` | | `Product` | Full product with price, images, modifiers, and nutrition | | `Modifier` | Modifier group with selection rules and items | | `ModifierItem` | Individual modifier option with price and max quantity | ## Next steps Add products to the cart with modifier selections. Collect customer details and process payments. # Error Codes Source: https://docs.craveup.com/guides/error-codes Handle public Storefront API errors without leaking credentials or replaying unsafe writes. Every Storefront API error has a stable machine-readable code and a request ID: ```json theme={null} { "code": "CART_CONFLICT", "message": "The cart changed. Refresh it and try again.", "requestId": "req_123", "details": {} } ``` ## SDK errors ```ts theme={null} import { StorefrontApiError, StorefrontClientStateError, StorefrontProtocolError, StorefrontTimeoutError, } from "@craveup/storefront-sdk"; try { await storefront.cart.addItem(locationId, cartId, payload); } catch (error) { if (error instanceof StorefrontApiError) { console.error(error.code, error.requestId); if (error.retryAfterMs !== undefined) scheduleRetry(error.retryAfterMs); } else if (error instanceof StorefrontClientStateError) { console.error(error.code); } else if (error instanceof StorefrontProtocolError) { console.error(error.reason, error.method, error.routeTemplate); } else if (error instanceof StorefrontTimeoutError) { showRetryMessage(); } } ``` `StorefrontApiError.retryAfterMs` is a bounded millisecond delay parsed from a valid `Retry-After` response header, or `undefined` when no usable delay was provided. `StorefrontClientStateError` is a local preflight failure; for example, `CUSTOMER_AUTH_REQUIRED` means no customer JWT was available and no request was sent. `StorefrontProtocolError.reason` is `EMPTY_RESPONSE` or `INVALID_JSON` when a successful response violates the SDK's JSON contract. Neither error retains response content, credentials, or secrets. Do not log capability, JWT, receipt-token, authorization, or payment-secret values with an error. ## Public error codes | Code | Typical status | Client behavior | | - | - | - | | `VALIDATION_ERROR` | 400 or 422 | Correct the fields described in `details` | | `UNAUTHORIZED` | 401 | Restore the matching cart capability or reauthenticate the customer | | `FORBIDDEN` | 403 | Stop; the authenticated principal does not own the resource | | `NOT_FOUND` | 404 | Treat the resource as unavailable; protected endpoints may conceal existence | | `RATE_LIMITED` | 429 | Honor `Retry-After` and back off | | `CART_CONFLICT` | 409 | Fetch the latest cart and require an explicit retry | | `RESOURCE_CONFLICT` | 409 | Refresh the affected customer resource before retrying | | `CART_IMMUTABLE` | 409 | Stop mutating a locked, completed, or expired cart | | `IDEMPOTENCY_KEY_REQUIRED` | 400 | Retry once with a stable unique key for that logical write | | `IDEMPOTENCY_KEY_REUSED` | 409 | Generate a new key because the old key belongs to different input | | `IDEMPOTENCY_IN_PROGRESS` | 409 | Poll or retry with bounded backoff using the same logical operation | | `DEPENDENCY_UNAVAILABLE` | 503 | Show a temporary failure and retry with backoff | | `INTERNAL_ERROR` | 500 | Show a generic message and retain the request ID for support | ## Retry rules Retry reads and explicitly retryable failures such as `RATE_LIMITED` or `DEPENDENCY_UNAVAILABLE` with exponential backoff. Prefer `retryAfterMs` when the API supplies it. Treat `StorefrontProtocolError` as an upstream contract or edge-transformation failure, retain its method and route template for support, and retry only when the underlying operation is safe. Never automatically replay a cart mutation after `CART_CONFLICT`, and never invent a new idempotency key while the outcome of an earlier write is unknown. ```ts theme={null} if (error instanceof StorefrontApiError && error.code === "CART_CONFLICT") { const cart = await storefront.cart.get(locationId, cartId); replaceCartState(cart); askCustomerToRetry(); } ``` See the revision and idempotency lifecycle. Review authentication boundaries and required headers. # Fulfillment Methods Source: https://docs.craveup.com/guides/fulfillment-methods Configure delivery, table-side, and room service for your storefront orders. Crave supports four fulfillment methods. This guide covers how to set each one on a cart and what data each method requires. ## Available methods | Method | Value | Use case | | - | - | - | | **Takeout / Pickup** | `takeout` | Customer picks up at the counter | | **Delivery** | `delivery` | Order delivered to customer's address | | **Table-side** | `table_side` | Dine-in, delivered to a table number | | **Room service** | `room_service` | Hotel/hospitality, delivered to a room | ## Check which methods are enabled Each location has a `methodsStatus` field indicating what's available. ```ts theme={null} const merchant = await storefront.merchant.getBySlug("downtown-pizza"); const location = merchant.locations[0]; console.log(location.methodsStatus); // { pickup: true, table: true, delivery: true, roomService: false } ``` Only show fulfillment options that are enabled for the location. ## Takeout (default) Takeout is the simplest method — just set the fulfillment method on the cart. ```ts SDK theme={null} await storefront.cart.update('loc_123', cartId, { fulfillmentMethod: 'takeout', }); ``` ```bash REST theme={null} curl -X PATCH "https://api.craveup.com/api/v1/storefront/locations/loc_123/carts/{cartId}" \ -H "X-Cart-Token: $CART_ACCESS_TOKEN" \ -H 'If-Match: "cart-2"' \ -H "Idempotency-Key: fulfillment_01" \ -H "Content-Type: application/json" \ -d '{"fulfillmentMethod": "takeout"}' ``` ## Delivery Delivery requires the customer's address with geographic coordinates for distance calculation and fee computation. Calculate the distance between the customer and the location to validate the delivery zone. Save the address to the cart. ### Check delivery distance ```ts SDK theme={null} const distance = await storefront.locations.distance('loc_123', { lat: 37.7749, lng: -122.4194, unit: 'miles', }); console.log(`${distance.distance.miles} miles away`); ``` ```bash REST theme={null} curl -X POST "https://api.craveup.com/api/v1/storefront/locations/loc_123/distance" \ -H "Content-Type: application/json" \ -d '{"lat": 37.7749, "lng": -122.4194, "unit": "miles"}' ``` ### Set delivery address ```ts SDK theme={null} await storefront.cart.setDelivery('loc_123', cartId, { street: '123 Main Street', city: 'San Francisco', state: 'CA', zipCode: '94105', country: 'United States', lat: 37.7749, lng: -122.4194, }); ``` ```bash REST theme={null} curl -X PUT "https://api.craveup.com/api/v1/storefront/locations/loc_123/carts/{cartId}/set-delivery" \ -H "X-Cart-Token: $CART_ACCESS_TOKEN" \ -H 'If-Match: "cart-3"' \ -H "Idempotency-Key: delivery_01" \ -H "Content-Type: application/json" \ -d '{ "street": "123 Main Street", "city": "San Francisco", "state": "CA", "zipCode": "94105", "country": "United States", "lat": 37.7749, "lng": -122.4194 }' ``` The `Address` type requires these fields: | Field | Type | Required | Description | | - | - | - | - | | `street` | `string` | Yes | Street address | | `streetOptional` | `string` | No | Apt/suite/floor | | `city` | `string` | Yes | City | | `state` | `string` | Yes | State/province code | | `zipCode` | `string` | Yes | Postal code | | `country` | `string` | Yes | Full supported country name (for example, `United States`) | | `lat` | `number` | Yes | Latitude | | `lng` | `number` | Yes | Longitude | Use a geocoding service (Google Places, Mapbox) to get `lat`/`lng` from the customer's address input. ## Table-side Table-side service sends the order to a specific table number within the restaurant. ```ts SDK theme={null} await storefront.cart.setTable('loc_123', cartId, '12'); ``` ```bash REST theme={null} curl -X PUT "https://api.craveup.com/api/v1/storefront/locations/loc_123/carts/{cartId}/set-table" \ -H "X-Cart-Token: $CART_ACCESS_TOKEN" \ -H 'If-Match: "cart-3"' \ -H "Idempotency-Key: table_01" \ -H "Content-Type: application/json" \ -d '{"tableNumber": "12"}' ``` Table numbers are numeric strings, such as `"12"` or `"12.5"`. Do not send labels or prefixes such as `"T-12"` or `"Patio 3"`. ## Room service Room service is designed for hotels and hospitality venues. It requires a room number and the guest's last name for verification. ```ts SDK theme={null} await storefront.cart.setRoom('loc_123', cartId, { roomNumber: '405', lastName: 'Johnson', }); ``` ```bash REST theme={null} curl -X PUT "https://api.craveup.com/api/v1/storefront/locations/loc_123/carts/{cartId}/set-room" \ -H "X-Cart-Token: $CART_ACCESS_TOKEN" \ -H 'If-Match: "cart-3"' \ -H "Idempotency-Key: room_01" \ -H "Content-Type: application/json" \ -d '{"roomNumber": "405", "lastName": "Johnson"}' ``` ## Fulfillment fees Each fulfillment method may have associated fees configured by the restaurant. These are automatically calculated and included in the cart totals: | Cart field | Description | | - | - | | `fulfillmentMethodFeeTotal` | Fulfillment fee amount in cart currency | Always render totals returned by the refreshed cart rather than recalculating fees in the client. ## Next steps Complete the order with customer details and payment. Set up Stripe for secure payment processing. # Generate a Private Integration API Key Source: https://docs.craveup.com/guides/generate-api-key Create and protect a server-only Crave credential for admin or partner integrations. Public storefronts do not use API keys. Browser and mobile apps call `/api/v1/storefront` directly with anonymous published reads, cart capabilities, and customer JWTs. Generate an API key only for a private admin, partner, or CLI integration that runs in a trusted server environment. ## Create the key 1. Open the [Dashboard Developers page](https://dashboard.craveup.com/dashboard/developers). 2. Sign in as an organization admin for the merchant account. 3. Create a key with a clear environment and service name. 4. Select only the permissions the server integration needs. Read and write permissions are independent; selecting write does not grant read. 5. Copy the token when it is shown, verify the displayed granted permissions, and put the token in the server platform's secret manager. ```env theme={null} # Server process only. Never expose this through a public frontend prefix. CRAVEUP_API_KEY=replace_with_secret ``` Send the token only in the private `X-API-Key` request header from your trusted server process. | Permission | Allows | | - | - | | `merchant:read` / `merchant:write` | Merchant and location settings reads or changes | | `catalog:read` / `catalog:write` | Menu, product, modifier, and discount reads or changes | | `orders:read` / `orders:write` | Order reads or lifecycle changes | | `customers:read` | Customer reads and exports | | `integrations:read` / `integrations:write` | Provider/integration status reads or configuration changes | Use separate credentials for development, staging, and production. Test deployments accept only `crv_test_` keys and production accepts only `crv_live_` keys. A wrong-tier, expired, revoked, or under-scoped key fails closed. Keys created before explicit permissions were introduced may show no scopes and can no longer call private scoped routes. Create a least-privilege replacement, update the consumer through its secret manager, verify the replacement, and then revoke the old key. Rotate or revoke any key that is exposed or no longer used. Expired and revoked keys do not count toward the active-key limit. If three active keys already exist, use the fourth slot only for replacement overlap and return to no more than three after cutover. Never embed a private integration key in browser JavaScript, an Expo bundle, a mobile app, a public repository, a URL, or client-visible logs. For storefront implementation, continue with the [Storefront SDK guide](/getting-started/storefront-sdk) or [REST API essentials](/getting-started/rest-api). # Glossary Source: https://docs.craveup.com/guides/glossary Key terms and concepts used across the Crave platform. ## A A server-only credential for private admin or partner integrations. Public Storefront API clients do not use an API key. A tracked action in the ordering funnel. Clients may fire cart-authorized `SCAN`, `CART_VIEW`, or `CHECKOUT_VIEW`; Crave records `ORDER_PLACED` server-side after authoritative order creation. See [Analytics guide](/guides/analytics). ## C A temporary container holding a customer's selected items, modifiers, fulfillment method, and order timing. Created when an ordering session starts. Identified by `cartId`. Carts expire after inactivity. The secret `cartAccessToken` returned when starting an ordering session. Persist it with the cart ID and revision in tab-scoped browser storage or secure mobile storage, and send it only in `X-Cart-Token`. A grouping of products within a menu (e.g., "Appetizers", "Mains", "Drinks"). Contains an ordered list of `productIds`. Displayed as tabs or sections in the storefront. Authentication provider used for the Admin API and Dashboard. Clerk manages user sessions, organization memberships, and JWT tokens. Not used for storefront customer-facing flows. ## D The geographic area a location delivers to. Checked via `storefront.locations.distance()` before setting a delivery address on a cart. A promo code (e.g., `SAVE10`) applied to a cart for a percentage or fixed discount. Applied via `storefront.cart.applyDiscount()`, removed via `storefront.cart.removeDiscount()`. ## F How a customer receives their order. Four options: `takeout` (counter pickup), `delivery` (address-based), `table_side` (dine-in to a table number), `room_service` (hotel room delivery). Set on the cart before checkout. See [Fulfillment Methods guide](/guides/fulfillment-methods). ## G An optional tip amount added during checkout. Locations configure whether tipping is enabled, suggested percentages, a default percentage, and whether custom amounts are allowed. Set via `storefront.cart.updateGratuity()`. ## L A physical or virtual restaurant location within a merchant organization. Each location has its own menus, hours, fulfillment methods, and payment settings. Identified by UUID or slug. ## M A collection of categories and products for a location. Locations can have multiple menus (e.g., Lunch, Dinner, Weekend Brunch). The active menu depends on the current time and the location's schedule. The top-level business entity. A merchant has one or more locations. Looked up by slug via `storefront.merchant.getBySlug()`. A set of customization options for a product (e.g., Size, Toppings, Extras). Each group has `min`/`max` selection rules. Contains modifier items. A single option within a modifier group (e.g., "Large", "Extra Cheese", "Bacon"). Has an optional price and max quantity. ## O The checkout result state returned to a customer: `payment_pending`, `order_pending`, `completed`, or `failed`. The entry point for a customer interaction. Starting a session returns the cart, its capability, and current revision. Created via `storefront.orderingSessions.start()`. ## P A Stripe PaymentIntent created for an authorized cart via `storefront.checkout.createPaymentIntent()`. Its `clientSecret` is passed to Stripe.js. See [Accept Payments guide](/guides/accept-payments). A menu item with name, description, price, images, and optional modifier groups. Added to carts with quantity and modifier selections. ## R A post-order experience rating (1-5 stars) with optional comment. Submitted via `storefront.ratings.submit()` while the completed cart capability is available. ## S A developer mode toggle that routes all operations through Stripe test mode. Orders are logged but not sent to the restaurant. Toggled via the Admin API's `/developer-mode` endpoint. A URL-safe identifier for merchants and locations (e.g., `downtown-pizza`). Used in storefront location paths as a human-readable alternative to UUIDs. The typed TypeScript client (`@craveup/storefront-sdk`) for the Storefront API. It manages public reads, cart capabilities/revisions, customer JWTs, checkout, receipts, ratings, and loyalty. See [SDK Reference](/getting-started/storefront-sdk). Stripe's platform for marketplace payments. Each restaurant connects their Stripe account to receive payouts. Crave handles the Connect setup; storefronts only need the publishable key for Stripe.js. ## W An event delivered through an HTTP POST. Crave currently receives private inbound provider webhooks, but does not yet publish an outbound merchant subscription contract. See the [outbound webhook availability guide](/guides/webhooks). # Clover POS Source: https://docs.craveup.com/guides/integrations/clover Understand Clover connection, merchant mapping, catalog discovery, and activation gates. Clover POS remains in development preview. An authorization connection is not proof that catalog, order routing, Ecommerce payment, or refunds are ready for production. ## Current connection flow An organization administrator completes Clover OAuth v2. Crave stores access and refresh credentials encrypted on the server and never returns them to Business Manager. The authorized Clover merchant must be explicitly mapped to one Crave location. Connecting does not automatically select Clover as the active payment processor. A manual provider sync retrieves minimized items, categories, modifiers, taxes, discounts, service charges, order types, stock, and opening hours. Crave stores immutable provider snapshots, maps them to canonical catalog identities, and reports conflicts or missing evidence without publishing a menu. An administrator may atomically publish a valid, current staged catalog. Order routing, provider checkout, refunds, stored cards, and gift cards remain separate capability gates. Existing carts and orders retain the authority captured when they were created. ## Safety boundaries * Crave never silently moves a Clover-owned checkout or refund to Stripe during an outage. * Browser card data must be tokenized by Clover; raw card numbers and security codes must not reach Crave servers. * Tips, stored cards, native gift cards, split tender, discounts, and partial returns require their own sandbox and entitlement evidence. * Verified Clover webhooks are durable reconciliation signals. Financial state is re-read from Clover before Crave treats it as authoritative. ## Availability Production activation is not generally available. Contact [hello@craveup.com](mailto:hello@craveup.com) for sandbox pilot requirements. # Square commerce setup Source: https://docs.craveup.com/guides/integrations/square Connect Square as the operational commerce authority for menu, checkout, orders, refunds, loyalty, and gift-card tenders. When an eligible Square connection is active for a location, Square is the source of truth for operational catalog data, order calculation, customer payments, refunds, loyalty, gift-card tenders, and the Square fulfillment lifecycle. Checkout remains embedded in Crave through Square Web Payments. Stripe is selected only for locations without an active Square connection; an in-progress cart never switches processors silently. ## Activation flow Authorize the requested merchant, customer, catalog, order, payment, refund, card, gift-card, inventory, location, and loyalty scopes. Bind the Crave location to the exact Square location after currency, timezone, and capability checks. Resolve catalog exceptions and activate a fresh, reviewed snapshot. Square owns price, tax, discounts, availability, and schedule after activation. Confirm checkout, webhook, and reconciliation health in the Dashboard before accepting traffic. Delivery additionally requires explicit Square Delivery certification and DoorDash Drive credentials. ## Authority rules * A connected and mapped Square location uses Square Payments. Stripe is not a fallback after Square has started a checkout. * Crave never receives raw card or gift-card data. Square Web Payments returns a token that the server uses with the mapped seller and location. * Square calculates the order using mapped catalog variations, automatic taxes, automatic discounts, service charges, and an optional order-linked Loyalty reward. A customer-entered Crave coupon is rejected for Square commerce. * Staff tip is recorded on the final Square Payment. DoorDash courier tip and the authoritative Drive fee plus tax are Square Order service charges. * Gift cards can partially authorize first; one final card, wallet, or stored card pays the remaining balance. Saved cards require authenticated customer ownership, buyer verification, and explicit consent. When a customer selects a saved card, the storefront passes its opaque Card ID back to Square Web Payments `Card.tokenize` with the current amount, currency, buyer contact, `customerInitiated=true`, and `sellerKeyedIn=false`. Only the resulting authenticated one-time payment token is used as the Square payment source; the deprecated separate `verificationToken` field is not part of the Crave payment contract. The selected Card ID remains server-side ownership evidence. The storefront sends the current versioned `cardSaveConsent` object only after the customer selects “Save this card securely with Square for future orders.” Before calling Square `CreateCard`, Crave writes immutable tenant-, customer-, connection-, and checkout-scoped consent evidence. Removing a saved card disables it in Square and records a separate revocation event. Crave stores a separate Square customer identity for each connected Square seller account, so cards and Loyalty never cross merchant boundaries when one Crave organization operates multiple Square accounts. * Authenticated customers can buy a new digital Square gift card in Crave. A non-gift-card Square payment must complete before activation; the card is linked to the same Square customer and its full number is returned once over a private, no-store response. Crave projections, logs, and webhook storage retain only safe IDs or redacted values. * Full and partial refunds return to gift-card tenders first, then the remaining card or wallet tenders. Mixed outcomes are surfaced as action required. * A refund of the payment that purchased a gift card triggers an idempotent Square `ADJUST_DECREMENT` activity with reason `PURCHASE_WAS_REFUNDED`, because refunding that funding payment does not reverse the loaded gift-card balance. * Square Loyalty replaces LIP for Square-authoritative locations. Enrollment, reward preview, issue, redemption, point accrual, and webhook projection use the Square customer and Loyalty account. * Checkout reads the member quote from `GET .../loyalty/quote`. A first-time member joins through the separate, explicitly consented `POST .../loyalty/enrollment` mutation before earning can begin; applying a reward never enrolls a customer as a side effect. Enrollment, reward apply, and reward removal require the current cart revision and an idempotency key, and each refreshes the Square payment session before payment can continue. * Enrollment requires a phone that the active Crave customer has verified by OTP. Checkout can add that factor to the current customer through `POST /customer/phone-verification` and confirm it through `POST /customer/phone-verification/confirmation`; an E.164-shaped profile value alone is not accepted as proof. Crave's one-time verification consent does not accept Square marketing text terms. * Customer loyalty history is location-scoped at `GET /customer/locations/{locationId}/loyalty/ledger`. A Square-authoritative location reads the current balance with `RetrieveLoyaltyAccount` and the reverse-chronological activity ledger with `SearchLoyaltyEvents` (up to 30 entries per page). It never falls back to LIP when Square is connected. The retained location binding keeps that exclusion in force if Square credentials or runtime health degrade, so Crave cannot mutate a second loyalty ledger through quote, cart, completion, cancellation, refund, or durable retry hooks. A queued LIP mutation rechecks the task location before delivery and terminates without a provider call after Square takes authority. Legacy tasks without location authority fail closed as well. * When an administrator approves a customer loyalty claim, Crave resolves the claim order's location before choosing the provider. A Square-authoritative order writes the approved correction through Square `AdjustLoyaltyPoints` with a stable provider idempotency key and retains the returned Square event ID. Provider errors leave the claim retryable; Crave never posts that adjustment to LIP. Orders at locations without Square authority continue to use the configured LIP program. * Completed Square orders accrue Loyalty points with an order-scoped provider idempotency key. A failed or ambiguous accrual is retained as a durable `LOYALTY_EFFECT` operation; the worker re-proves the completed Square tenders, current tenant and location binding, and returned Loyalty events before it marks the cart projection complete. * Vendor confirmation, ready-for-pickup, and completion actions update the exact Square fulfillment before Crave commits the matching local state. Each update uses the current Square order version, fulfillment UID, and a stable idempotency key. Square-originated fulfillment webhooks apply locally without echoing the same transition back to Square. ## Delivery with DoorDash Drive Delivery is fail-closed until the location has fresh capability evidence whose source is `square_live_pos_delivery_certification`, a selected `DOORDASH_DRIVE` provider, and valid Drive credentials. Crave requests a five-minute Drive quote, fingerprints its address and cart inputs, adds `fee + tax` and courier tip to the Square Order, pays that exact order, then accepts the same Drive quote. Definite dispatch rejection triggers idempotent Square refund compensation. An ambiguous timeout is reconciled using the same external delivery ID and is never auto-refunded while a courier job might exist. DoorDash webhooks update Crave tracking and the Square delivery fulfillment. Active deliveries are also retrieved every 60 seconds when their last reconciliation is more than two minutes old, so a missing webhook does not strand tracking state. Order cancellation reaches a conclusive DoorDash cancel state before Square refund processing begins. ## Apple Pay domains Apple Pay requires the storefront's exact HTTPS domain to be registered for this Square application. Crave proxies Square's current `apple-developer-merchantid-domain-association` file at `/.well-known/apple-developer-merchantid-domain-association` without an extension or long-lived cache. Then add the host under **Apple Pay** in Square Developer Console, using Sandbox mode for a sandbox storefront and Production mode for production. Apple Pay cannot be verified on localhost. ## Webhooks Create the Square subscription against: ```text theme={null} https:///api/v1/webhooks/square ``` Subscribe to the exact events handled by this release: ```text theme={null} catalog.version.updated inventory.count.updated location.created location.updated order.created order.updated order.fulfillment.updated payment.created payment.updated refund.created refund.updated customer.created customer.updated customer.deleted card.created card.updated card.disabled card.forgotten card.automatically_updated loyalty.account.created loyalty.account.updated loyalty.account.deleted loyalty.event.created loyalty.program.created loyalty.program.updated loyalty.promotion.created loyalty.promotion.updated gift_card.created gift_card.updated gift_card.customer_linked gift_card.customer_unlinked gift_card.activity.created gift_card.activity.updated oauth.authorization.revoked ``` Set `SQUARE_WEBHOOK_SIGNATURE_KEY` only in the sandbox secret manager. Never paste it into tickets, chat, source control, or browser configuration. Crave verifies Square's signature over the untouched body and exact notification URL, then acknowledges only after durable inbox storage. For DoorDash Drive, register: ```text theme={null} https:///api/v1/webhooks/doordash ``` Configure the same Basic or OAuth `Authorization` value in DoorDash and the `DOORDASH_WEBHOOK_AUTHORIZATION` sandbox secret. Crave compares the exact value in constant time, durably stores the event, serializes delivery projections, and retries failures. ## Health and recovery The Square integration page shows authorization and mapping status, capability readiness, last reconciliation, last webhook receipt, and pending or failed events. Use **Reconcile** to refresh locations, catalog, inventory, orders, payments, refunds, customers, cards, Loyalty programs, promotions, accounts, events and rewards, plus gift cards and their location activity. Gift-card account numbers are redacted before reconciliation evidence is stored. Use **Retry events** after repairing a dependency. A webhook HTTP success means the event was stored; projection occurs asynchronously and remains observable. The runtime also enqueues one tenant-scoped full completeness sweep for every mapped Square location in each UTC day. The durable operation pins the exact organization, connection, and binding, runs the same canonical path as the operator action, and records the successful system reconciliation in the health timeline. Provider or network failures use bounded exponential retry; an expired read-only lease is safe to replay, while changed binding evidence fails closed for operator review. Faster catalog, webhook, payment/refund, checkout-abandonment, Loyalty, and DoorDash workers continue to repair their own nonterminal domains between full sweeps. ## Admin endpoints | Method | Path | Purpose | | - | - | - | | `POST` | `/locations/:id/integrations/square/oauth` | Start OAuth | | `GET` | `/locations/:id/integrations/square` | Status, health, capabilities | | `GET` | `/locations/:id/integrations/square/locations` | List Square locations | | `POST` | `/locations/:id/integrations/square/locations` | Bind the Square location | | `POST` | `/locations/:id/integrations/square/catalog/import` | Import the catalog | | `POST` | `/locations/:id/integrations/square/reconcile` | Run full reconciliation | | `POST` | `/locations/:id/integrations/square/events/retry` | Retry failed webhook events | | `DELETE` | `/locations/:id/integrations/square` | Disconnect after deactivation | Disconnecting preserves canonical catalog and historical evidence. Active or degraded commerce modules must be deactivated or rolled back first. # Stripe Payments Source: https://docs.craveup.com/guides/integrations/stripe Set up Stripe Connect for payment processing in your storefront. Crave uses [Stripe Connect](https://stripe.com/connect) to process payments. Each restaurant connects their own Stripe account to receive payouts directly. This guide covers the full Stripe setup. ## How it works When you sign up, Crave provisions a Stripe Connect Express account for your organization. Stripe collects your bank details, identity verification, and tax information. Your storefront uses Stripe.js with the connected account to securely collect card details. Funds are routed to your connected Stripe account after Crave's platform fee. ## Prerequisites * A Crave account with at least one location * A bank account for payouts * Government-issued ID for verification (Stripe requirement) ## Complete Stripe onboarding 1. In the Dashboard, go to **Settings > Payments** 2. Click **Complete Stripe Setup** 3. Stripe's embedded onboarding form collects: * Business type (individual, company, non-profit) * Business address and phone * Bank account or debit card for payouts * Identity verification (SSN or ID upload) * Tax information (EIN or SSN) 4. Once submitted, Stripe verifies your information (usually instant, sometimes 1-2 business days) You can start building your storefront immediately. Stripe onboarding only needs to be complete before accepting real payments. ## Storefront integration Your storefront only needs the **Stripe publishable key** on the client. The secret key and connected account routing are handled by Crave's backend. ### Environment setup ```env filename=".env.local" theme={null} NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=pk_live_your_key ``` For test mode, use the test publishable key (`pk_test_...`). ### Payment flow ```tsx filename="app/checkout/page.tsx" theme={null} import { loadStripe } from "@stripe/stripe-js"; import { Elements } from "@stripe/react-stripe-js"; // 1. Create a capability-protected payment session via Crave const payment = await storefront.checkout.createPaymentSession( locationId, cartId, ); if (payment.provider !== "STRIPE") throw new Error("Square owns this checkout"); // 2. Initialize Stripe only for a Stripe session const stripePromise = loadStripe( process.env.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY!, ); // 3. Wrap checkout UI with Elements provider ; ``` For the full implementation, see the [Accept Payments guide](/guides/accept-payments). ## Testing In test mode, Crave automatically uses Stripe's test environment: | Card | Scenario | | - | - | | `4242 4242 4242 4242` | Successful payment | | `4000 0000 0000 3220` | 3D Secure required | | `4000 0000 0000 0002` | Card declined | Toggle test/live mode from the Dashboard under **Settings > Developer > Mode**. View the current payout schedule and fee details in the connected Stripe Dashboard. Do not hard-code payout timing or commercial rates in storefront code. ## Troubleshooting ### "Stripe onboarding incomplete" * Return to **Settings > Payments** and click **Complete Stripe Setup** * Stripe may need additional documents for verification * Check your email for messages from Stripe ### Payments failing in production * Verify you're using the **live** publishable key (`pk_live_...`), not the test key * Ensure the restaurant's Stripe Connect account is fully verified * Check the Stripe Dashboard for declined payment details # Toast POS Source: https://docs.craveup.com/guides/integrations/toast Connect Toast POS to sync menus and orders with your Crave storefront. Toast POS integration is currently in development. This page will be updated with setup instructions, API details, and catalog sync documentation when the integration is available. ## Planned features | Feature | Description | | - | - | | **Menu sync** | Import menus, categories, and items from Toast | | **Order forwarding** | Push storefront orders to Toast for kitchen display and fulfillment | | **Price sync** | Keep Crave prices in sync with Toast catalog changes | | **Modifier mapping** | Map Toast modifier groups to Crave modifiers | ## How it will work Enter your Toast API key and restaurant GUID in the Crave Dashboard. Select which Toast restaurant maps to your Crave location. Pull menus and items from Toast into Crave's menu editor. Orders placed on your storefront are automatically sent to Toast. ## Get notified To be notified when the Toast integration launches, contact [hello@craveup.com](mailto:hello@craveup.com) or join our [Slack community](https://join.slack.com/t/cravejscommunity/shared_invite/zt-3hfr081io-aYuLC3n6gBFKQuho0HrTng). # Manage Cart Source: https://docs.craveup.com/guides/manage-cart Create and mutate a cart with capability, revision, and idempotency protection. Every cart has three values that belong together: `cart.id`, the secret `cartAccessToken`, and the current `cart.revision`. The SDK persists them through the `sessionStore` you provide. ## Start a session ```ts theme={null} const session = await storefront.orderingSessions.start("loc_123", { marketplaceId: "web", fulfillmentMethod: "takeout", }); const cartId = session.cart.id; ``` ```bash theme={null} curl -X POST "https://api.craveup.com/api/v1/storefront/locations/loc_123/ordering-sessions" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: start_01" \ -d '{"marketplaceId":"web","fulfillmentMethod":"takeout"}' ``` `marketplaceId` is optional source attribution, such as `"web"` or `"mobile"`; it is not a location ID. The route already supplies the location scope. Store the response capability in `sessionStorage` for browsers or secure device storage for mobile. Do not use a URL, cookie shared across origins, analytics property, or application log. ## Add an item ```ts theme={null} const cart = await storefront.cart.addItem("loc_123", cartId, { productId: "prod_burger", quantity: 1, itemUnavailableAction: "remove_item", selections: [ { groupId: "mod_size", selectedOptions: [{ optionId: "opt_large", quantity: 1 }], }, ], }); ``` ```bash theme={null} curl -X POST "https://api.craveup.com/api/v1/storefront/locations/loc_123/carts/cart_456/items" \ -H "Content-Type: application/json" \ -H "X-Cart-Token: $CART_ACCESS_TOKEN" \ -H 'If-Match: "cart-0"' \ -H "Idempotency-Key: add_item_01" \ -d '{"productId":"prod_burger","quantity":1,"selections":[],"itemUnavailableAction":"remove_item"}' ``` ## Read and mutate the cart ```ts theme={null} const current = await storefront.cart.get("loc_123", cartId); await storefront.cart.updateItemQuantity("loc_123", cartId, "item_abc", 2); await storefront.cart.removeItem("loc_123", cartId, "item_abc"); await storefront.cart.applyDiscount("loc_123", cartId, "SAVE10", { includeCustomerContext: true, }); await storefront.cart.removeDiscount("loc_123", cartId); ``` The SDK sends the capability automatically. For raw REST calls, send `X-Cart-Token` on every cart read and write. Send `If-Match` and a stable `Idempotency-Key` on every mutation. Pass `includeCustomerContext: true` only when a signed-in shopper still has a guest-capability cart and discount validation needs customer context. Omit it for a guest; a claimed cart uses the customer JWT automatically. ## Handle conflicts safely The API returns `CART_CONFLICT` when `If-Match` is stale. The SDK refreshes the stored revision but deliberately does not repeat the failed mutation. Replace the UI cart with the fresh server state, explain the change, and require the user to retry. ## End the capability lifecycle `cart.delete()` clears the SDK session. `cart.claim()` removes the guest capability while retaining the authoritative revision for customer-authenticated access. After checkout reaches `completed` or `failed`, clear the persisted capability once terminal handling such as a same-page rating is finished. Protect payment creation and order-result polling. Handle authorization and concurrency failures. # Developer Package and Crave Cloud Source: https://docs.craveup.com/guides/open-source-and-cloud How Crave's developer-facing package fits into the managed Crave Cloud platform. Crave pairs a documented developer surface with a managed cloud product: the integration contract is explicit, while production franchise operations remain in a managed control plane. The Storefront SDK is publicly downloadable from npm but is marked `UNLICENSED`. Its canonical source repository is private, and npm availability or public documentation does not grant permission to copy, modify, or redistribute the package. Contact [hello@craveup.com](mailto:hello@craveup.com) for licensing questions. Crave Cloud is where brands coordinate real locations, roles, integrations, payment routing, customer identity, loyalty, and operational readiness. ## How to think about it | Layer | Use it for | Operational owner | | - | - | - | | **Crave.js packages** | Storefront SDK, CLI, examples, components, and local developer workflows | Your engineering team | | **Storefront API** | Customer-facing ordering flows across web, mobile, kiosk, and custom channels | Shared between your app and Crave | | **Crave Cloud** | Business Manager, locations, roles, identity boundaries, integrations, payment routing, observability, and readiness | Crave-managed cloud | | **Bring-your-own systems** | Existing loyalty, ordering, identity, or payment systems that a brand wants to keep | Joint integration ownership | ## Managed cloud is the production default Production restaurant franchise infrastructure is not just a package install. It needs secure roles, location readiness, integration monitoring, audit trails, payment routing, and support for operational edge cases across many stores. Use Crave Cloud when you need: * multiple locations, franchisees, or business entities * staff roles and Business Manager access * payment routing between corporate, franchisee, or location accounts * monitored integrations with ordering, loyalty, payments, or identity providers * supportable production operations ## Public developer surface Use the Crave.js developer surface when you need: * a local storefront you can run and customize * typed SDK calls for menus, carts, checkout, payments, and analytics * React components that can live inside your application * REST examples for non-TypeScript clients * clear contracts for webhooks and API behavior Start with the CLI, SDK, API, and UI components. See how storefronts connect to identity, ordering, loyalty, payments, and operations. ## What is not self-hosted The documented Crave.js developer surface does not make the SDK package or every part of Crave Cloud open source or self-hostable. Business Manager, production identity boundaries, managed loyalty, payment routing, integration health, and franchise operations are managed platform capabilities unless your Crave agreement says otherwise. This keeps the developer experience flexible without pushing the operational burden of a franchise platform onto every storefront team. # Order Tracking Source: https://docs.craveup.com/guides/order-tracking Poll the authoritative checkout result and display a protected receipt. After Stripe redirects back to the storefront, recover the cart session from tab-scoped storage and poll the protected order-result endpoint. ## Result states | State | Meaning | | - | - | | `payment_pending` | Stripe has not reached a terminal payment state | | `order_pending` | Payment succeeded and Crave is finalizing the order | | `completed` | The response contains the public order detail | | `failed` | Checkout reached a terminal failure with a machine-readable code | ```ts theme={null} async function waitForOrder(locationId: string, cartId: string) { for (let attempt = 0; attempt < 40; attempt += 1) { const result = await storefront.checkout.getOrderResult(locationId, cartId); if (result.state === "completed" || result.state === "failed") return result; await new Promise((resolve) => setTimeout(resolve, Math.min(1000 + attempt * 250, 3000)), ); } throw new Error("Order confirmation is taking longer than expected."); } ``` Do not treat a redirect, cart lock, timeout, or missing response as success. Keep the capability until terminal handling finishes, then clear it from persisted storage. ## Receipt links Guest receipt links place a receipt capability in the URL fragment so browsers do not send it to servers as part of the request URL. Capture it in the browser, immediately remove it with `history.replaceState`, store it in versioned, tab-scoped `sessionStorage` keyed by API environment, merchant, and receipt ID, and send it as `X-Receipt-Token`: ```ts theme={null} const order = await storefront.receipts.get(receiptId, { receiptToken }); ``` A signed-in customer can access their own order through the customer JWT instead. Receipt identifiers alone never authorize access. ## Ratings Submit a rating while the completed cart capability is still available in memory: ```ts theme={null} await storefront.ratings.submit(locationId, cartId, { rating: 5, comment: "Great food and fast service.", }); ``` Handle terminal and retryable failures. Review all checkout and receipt methods. # Testing Source: https://docs.craveup.com/guides/testing Test storefront capability, concurrency, checkout, and secret-handling behavior. Use sandbox API and Stripe publishable values for local and CI runs: ```env theme={null} NEXT_PUBLIC_CRAVEUP_API_URL=https://dev-api-43233223.craveup.com NEXT_PUBLIC_CRAVEUP_LOCATION_ID=loc_test NEXT_PUBLIC_CRAVEUP_MERCHANT_SLUG=test-cafe NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=pk_test_xxx ``` ## Unit tests Test the caller-owned session adapter independently: * versioned `sessionStorage` keys include the canonical API environment, merchant, and location; * the cart capability and customer JWT never enter URLs, analytics, or `localStorage`; * ETag revisions are retained; * claim, delete, expiry, and terminal checkout handling clear persisted capability state; * malformed or old-version records fail closed. Mock `fetch` when testing the SDK: ```ts theme={null} const fetchMock = vi.fn( async () => new Response(JSON.stringify(cart), { status: 200, headers: { "Content-Type": "application/json", ETag: '"cart-4"' }, }), ); const storefront = createStorefrontClient({ baseUrl: "https://api.example.test", fetch: fetchMock, sessionStore, }); ``` Assert cart calls send the capability, current `If-Match`, and an idempotency key. For `CART_CONFLICT`, assert the mutation is not replayed automatically and the current cart revision is refreshed. ## Checkout tests Cover every authoritative result: * `payment_pending` continues bounded polling; * `order_pending` continues bounded polling; * `completed` renders the returned order and clears persisted cart state after terminal handling; * `failed` renders failure, never falls back to success, and clears persisted cart state after terminal handling; * authorization, timeout, and stale-revision errors remain visible. ## Receipt tests Start with a URL containing `#receiptToken=...`. Assert the app calls `history.replaceState` immediately, stores the token only for that API environment, merchant, and receipt ID in `sessionStorage`, and sends it in the receipt header—not in the request URL. ## End-to-end sandbox flow 1. Fetch published merchant, location, menu, product, distance, order-time, and gratuity data anonymously and verify no customer JWT is attached. 2. Create a cart and verify the capability is returned only once for a new cart. 3. Mutate the cart and verify revision changes. 4. Create a PaymentIntent and confirm with a sandbox payment method. 5. Observe nonterminal polling followed by exactly one terminal result. 6. Verify rating and receipt access. 7. Confirm no secret or capability appears in browser URLs, logs, analytics, or static assets. Run the same build artifact you intend to promote. # Theming Source: https://docs.craveup.com/guides/theming Customize your storefront's colors, fonts, and component styles. Crave storefront templates are built with [Tailwind CSS](https://tailwindcss.com) and [shadcn/ui](https://ui.shadcn.com). You customize the look by editing CSS variables and Tailwind config — no proprietary theming API to learn. ## How theming works Every template ships with a `globals.css` file that defines CSS custom properties. Tailwind maps these variables to utility classes, and all Crave components use those classes. Change a variable, and every component updates. ```css filename="src/app/globals.css" theme={null} @layer base { :root { --background: 0 0% 100%; --foreground: 240 10% 3.9%; --primary: 160 50% 22%; /* your brand color */ --primary-foreground: 0 0% 98%; --accent: 160 30% 96%; --radius: 0.5rem; } .dark { --background: 240 10% 3.9%; --foreground: 0 0% 98%; --primary: 160 50% 40%; --primary-foreground: 0 0% 98%; } } ``` ## Change brand colors Update the `--primary` variable to match your brand. Use HSL values (hue, saturation, lightness): ```css filename="src/app/globals.css" theme={null} :root { --primary: 210 100% 50%; /* blue brand */ --primary-foreground: 0 0% 100%; /* white text on primary */ } ``` All buttons, links, active states, and accent elements update automatically. Use the [shadcn/ui theme generator](https://ui.shadcn.com/themes) to preview color combinations and copy the CSS variables. ## Customize fonts Templates use `next/font` for self-hosted fonts with zero layout shift. Update the font in `layout.tsx`: ```tsx filename="src/app/layout.tsx" theme={null} import { Inter } from 'next/font/google'; // or use a local font: // import localFont from 'next/font/local'; const font = Inter({ subsets: ['latin'] }); export default function RootLayout({ children }: { children: React.ReactNode }) { return ( {children} ); } ``` For local font files (`.woff`, `.woff2`): ```tsx filename="src/app/layout.tsx" theme={null} import localFont from 'next/font/local'; const brandFont = localFont({ src: './fonts/BrandFont-Regular.woff2', variable: '--font-brand', }); export default function RootLayout({ children }: { children: React.ReactNode }) { return ( {children} ); } ``` Then reference the variable in your Tailwind config: ```ts filename="tailwind.config.ts" theme={null} export default { theme: { extend: { fontFamily: { sans: ['var(--font-brand)', 'system-ui', 'sans-serif'], }, }, }, }; ``` ## Customize component styles Crave components use Tailwind utility classes with shadcn/ui's `cn()` helper. Override styles by passing `className`: ```tsx theme={null} ``` For deeper customization, edit the component source directly. All components live in your project's `src/components/` directory — they are not locked in a package. ## Border radius Control the roundness of all components with the `--radius` variable: ```css filename="src/app/globals.css" theme={null} :root { --radius: 0.75rem; /* more rounded */ /* --radius: 0rem; /* sharp corners */ } ``` ## Dark mode Templates support dark mode out of the box using Tailwind's `dark:` variant. Toggle dark mode by adding the `dark` class to the `` element: ```tsx theme={null} // Toggle dark mode document.documentElement.classList.toggle('dark'); ``` For Next.js apps, use [next-themes](https://github.com/pacocoursey/next-themes) for automatic system preference detection: ```tsx filename="src/app/providers.tsx" theme={null} 'use client'; import { ThemeProvider } from 'next-themes'; export function Providers({ children }: { children: React.ReactNode }) { return ( {children} ); } ``` ## Restaurant branding Load the restaurant's branding dynamically from the API: ```tsx theme={null} const merchant = await storefront.merchant.getBySlug('downtown-pizza'); // Use merchant data for dynamic theming {merchant.name}
``` Each merchant location also exposes `restaurantLogo` and `coverPhoto` for location-specific branding. ## Next steps See all available storefront components and their props. End-to-end guide for building a custom storefront. # Troubleshooting Source: https://docs.craveup.com/guides/troubleshooting Diagnose direct Storefront API configuration, capability, concurrency, and checkout failures. ## The SDK says an explicit base URL is required Set `NEXT_PUBLIC_CRAVEUP_API_URL` to the API origin and rebuild. Do not append `/api/v1/storefront`; the SDK owns that path. ## Browser requests fail CORS Add the exact storefront origin, including scheme and host, to the API service's `CORS_ALLOWED_ORIGINS`. Avoid wildcard origins. Confirm the preflight allows the headers used by the failed request. ## Published data returns 404 Public catalog routes deliberately hide unpublished, inactive, or cross-tenant resources as not found. Verify the merchant/location is published and that the location belongs to the requested merchant. ## Cart request returns 401 The cart capability is absent, invalid, or belongs to another cart/location. Start a new ordering session if the saved capability is stale. Never substitute a private API key. ## Cart request returns `CART_CONFLICT` Another mutation changed the cart revision. Fetch the cart, update the saved revision from `ETag`, present the current state, and let the shopper retry. Do not silently replay the mutation. ## Cart request returns `CART_IMMUTABLE` The cart is locked, completed, or expired. Preserve terminal checkout handling when applicable; otherwise clear the stale session and create a new cart. ## Customer request returns 401 Confirm login and OTP requests used the same `merchantSlug`, and that `getAuthToken` returns the current customer JWT. Clear the JWT and restart login when the session is expired or revoked. ## Checkout appears stuck Poll `checkout.getOrderResult` with bounded backoff. `payment_pending` and `order_pending` are not failures or success. Render success only for `completed`; render the supplied code for `failed`. Surface timeouts rather than manufacturing an order. ## Receipt is unavailable Receipt access requires either the authorized customer JWT or the receipt capability. For email/SMS links, capture `receiptToken` from the fragment, remove the fragment immediately, and send the token through the SDK. A missing, wrong, or expired token intentionally looks like not found. ## PaymentIntent errors Check cart revision first, then confirm the API service has matching provider credentials for the selected environment. The browser receives only the PaymentIntent client secret and Stripe publishable key. ## Useful error fields `StorefrontApiError` provides `status`, `code`, `message`, `requestId`, `details`, `method`, and `routeTemplate` without retaining URLs, bodies, or credentials. Include `requestId` in support reports. # Upgrade Guide Source: https://docs.craveup.com/guides/upgrade-guide Migrate a Storefront SDK 1.x integration to the direct, capability-safe 2.0.0 contract. Storefront SDK `2.0.0` is a major version. It replaces the published 1.x browser API-key client with the direct public `/api/v1/storefront` contract. Treat the upgrade as an application migration: use a branch, update all callers together, and verify a sandbox order before production. Published 1.x artifacts remain a durable npm history. Do not unpublish them or silently replace their contents. They may be deprecated after `2.0.0` is verified, but 2.x does not include an API-key shim, a dual transport, or duplicate names for the same operation. Use the canonical `cart.applyDiscount()`, `cart.removeDiscount()`, and location-scoped `loyalty.ledger(locationId)` methods. ## Install the exact major ```bash theme={null} pnpm remove @craveup/storefront-sdk pnpm add --save-exact @craveup/storefront-sdk@2.0.0 ``` Commit the updated lockfile. Do not use `latest`, a semver range, a Git URL, or a workspace build in a released storefront. ## Replace configuration Remove the 1.x browser configuration: ```ts theme={null} // 1.x — remove this configuration. createStorefrontClient({ apiKey: process.env.NEXT_PUBLIC_CRAVEUP_API_KEY!, }); ``` Create a 2.x browser client with public configuration and caller-owned stores: ```ts theme={null} const storefront = createStorefrontClient({ baseUrl: process.env.NEXT_PUBLIC_CRAVEUP_API_URL!, sessionStore, getAuthToken: () => customerTokenStore.get(), }); ``` Create a separate server client with only `baseUrl`; use it for anonymous published reads. Remove browser/mobile API keys, mint endpoints, BFF proxies added only for the old SDK, and hand-built authorization headers. ## Update methods, arguments, and types | 1.x surface | 2.0.0 surface | | - | - | | `locations.getDistance(locationId, input)` | `locations.distance(locationId, input)` | | `locations.submitRating(locationId, { cartId, rating, comment })` | `ratings.submit(locationId, cartId, { rating, comment })` | | `payments.createIntent(locationId, cartId)` | `checkout.createPaymentSession(locationId, cartId)` | | `cart.getRecommendations(locationId, cartId)` | `cart.recommendations(locationId, cartId)` | | `discounts.apply(locationId, { cartId, code })` | `cart.applyDiscount(locationId, cartId, code)` | | `discounts.remove(locationId, cartId)` | `cart.removeDiscount(locationId, cartId)` | | `client.http.*` and `RequestOptions.skipAuth` | Removed; use the typed namespaces and SDK-owned authorization boundaries | | `StorefrontAnalyticsEventType` enum | `StorefrontAnalyticsEvents` constant plus `StorefrontAnalyticsEvent` union | | Client-supplied `ORDER_PLACED` | Removed; Crave records it after authoritative order creation | | `StorefrontRecommendedProduct` / `RecommendedProductsResponse` | `RecommendationProduct` / `RecommendationProduct[]` | | `SubmitRatingPayload` / `SubmitRatingResponse` | `RatingRequest` / `RatingResponse` | | `GetLocationViaSlugType` | `StorefrontLocation` | | `LocationAddressDTO` | `DeliveryAddress` | | `ApiError` | `StorefrontApiError`, `StorefrontProtocolError`, `StorefrontTimeoutError`, or `StorefrontClientStateError` | Only `SCAN`, `CART_VIEW`, and `CHECKOUT_VIEW` remain valid client analytics events. `StorefrontClientStateError` provides a typed local preflight failure, including `CUSTOMER_AUTH_REQUIRED`, before a request is sent without required customer authentication. ## Update response handling | Operation | 1.x response | 2.0.0 response | | - | - | - | | `orderingSessions.start` | `{ cartId }` or `{ errorMessage }` | `{ cart, cartAccessToken? }`; the SDK persists the capability and revision through `sessionStore` | | `cart.addItem` | `{ cartId, cart }` | `StorefrontCart` | | `cart.update` | Partial cart object | Authoritative `StorefrontCart` | | `cart.updateOrderTime`, `cart.validateAndUpdateCustomer`, `cart.setDelivery`, `cart.setTable`, `cart.setRoom`, `cart.delete` | `{ success: true }` | Authoritative `StorefrontCart` | | Apply or remove discount | `{ success: true }` | Authoritative `StorefrontCart` | | Create PaymentIntent | `{ clientSecret, stripeAccountId }` | `{ clientSecret }`; Stripe account routing is server-owned | Do not rely on structural casts to bridge these response changes. Remove wrapper access such as `result.cart`, replace success-only branches with the returned authoritative cart, and recompile every caller against 2.0.0. Product fixtures and adapters must now provide the required `Product.locationId`. `OrderTimesResponse` exposes labeled `orderDays`; replace the legacy regular-day `{ from, to }` interval branch and render only the days and intervals returned by the API. ## Update request validation * Use only `takeout`, `table_side`, `room_service`, or `delivery` for fulfillment. Set table and room identifiers through `cart.setTable()` and `cart.setRoom()` rather than a generic cart patch. * Use `cart.updateOrderTime()` for scheduling. `cart.update()` accepts fulfillment and/or a note, not order-date or order-time fields. * Send exactly one of `amount` or `percentage` to `cart.updateGratuity()`. * Send a full supported country name such as `United States`, not `US`. * Send table numbers as numeric strings such as `"12"`, not `"T-12"` or `"Patio 3"`. * Treat `marketplaceId` as optional source attribution such as `"web"` or `"mobile"`; it is not a location ID. * For a signed-in shopper who still has a guest-capability cart, use the named `{ includeCustomerContext: true }` request option only when customer-aware discount validation or Stripe customer association is needed. Omit it for a guest; claimed carts use the customer JWT automatically. ## Required migration 1. Configure the explicit public API origin for the correct environment. 2. Provide a `StorefrontSessionStore` for `{ locationId, cartId, accessToken, revision }`, scoped by canonical API environment, merchant, and location. 3. Create carts through `orderingSessions.start()`. Let the SDK persist the returned capability and authoritative revision; do not copy the token into application state or UI props. 4. Let cart methods attach `X-Cart-Token`, `If-Match`, and `Idempotency-Key`. On `CART_CONFLICT`, refresh and ask the customer to retry; never silently replay intent. 5. Store the merchant-bound customer JWT in versioned, tab-scoped browser storage or `expo-secure-store`, and clear it on logout. 6. Replace legacy payment calls with `checkout.createPaymentSession()`, branch on the selected provider, and use bounded `checkout.getOrderResult()` polling; stop reading the removed `stripeAccountId` response field. 7. Capture a receipt capability from the URL fragment, remove the fragment immediately, and scope storage by environment, merchant, and receipt ID. 8. Register each exact deployed browser origin with Crave. Native apps normally need no CORS registration; Expo web does. 9. Delete the obsolete 1.x client, API-key configuration, wrapper types, adapters, tests, and documentation in the same application change. ## Verification checklist * Public bundles contain no private Crave or payment-provider secrets. * Published merchant, location, menu, product, distance, order-time, and gratuity requests carry no customer JWT. * Wrong, expired, cross-location, and cross-merchant capabilities are denied. * Stale revisions return `CART_CONFLICT` and are not automatically replayed. * Only `completed` is treated as checkout success. * CORS is restricted to the exact deployed storefront origins. * A clean install, typecheck, production web build or Expo export, and sandbox vertical slice use the exact registry artifact. If a production 1.x storefront cannot move atomically, keep its pinned 1.x artifact and deployed API path unchanged while a separate 2.0.0 release candidate is verified. Do not make one bundle switch between the two contracts at runtime. # Outbound Webhooks (Planned) Source: https://docs.craveup.com/guides/webhooks Current availability and integration guidance for outbound merchant events. Crave does not currently offer a public outbound webhook subscription service. There is no merchant endpoint configuration screen, external event catalog, signing-secret flow, delivery log, retry contract, or production support commitment to integrate against yet. ## Current boundary Crave receives inbound provider webhooks from services such as Stripe, Square, and DoorDash. Those provider callbacks are private Crave infrastructure and are not an outbound developer event API. The repository contains persistence foundations for future outbound delivery, but the endpoint registry, event producers, delivery worker, signing contract, operator UI, and external acceptance evidence are not complete. Older examples that referenced events such as `order.created` or `cart.abandoned`, the `X-Crave-Signature` header, fixed retry delays, or a **Settings > Webhooks** screen are not implemented contracts and must not be used for production integrations. ## What to use today * Use the Storefront API and SDK order-result resources for guest checkout outcomes. * Use private, server-side management API keys only for the documented scoped routes. * Use provider-specific integration guides only for the inbound provider configuration they explicitly describe. * Coordinate polling, reconciliation, or brand-specific event requirements with your Crave integration owner before depending on them. ## Planned contract The outbound webhook contract will be published only after its endpoint lifecycle, versioned event schemas, signature and replay policy, retry and deduplication behavior, tenant isolation, delivery observability, and sandbox conformance tests are implemented. Event names and headers remain subject to change until that release. Review the currently supported public and private request boundaries. Validate the API behavior that is available today. # Flutter quickstart Source: https://docs.craveup.com/quickstarts/flutter Connect a Flutter application to the Crave Storefront API with typed, capability-safe ordering. This quickstart configures the official Dart SDK, checks whether takeout ordering is available, and loads a published menu without a browser API key or BFF. ## 1. Install ```yaml pubspec.yaml theme={null} dependencies: flutter: sdk: flutter crave_storefront_sdk: 0.2.0 ``` Run `flutter pub get`. ## 2. Configure deployment values Pass deployment-specific values with `--dart-define`: ```bash theme={null} flutter run \ --dart-define=CRAVEUP_API_URL=https://api.craveup.com \ --dart-define=CRAVEUP_MERCHANT_SLUG=downtown-pizza \ --dart-define=CRAVEUP_LOCATION_ID=loc_123 ``` For sandbox testing, replace the origin and identifiers with the exact sandbox deployment values provided for your account. Sandbox and production have separate environment variables and data; never reuse a `StorefrontSessionStore` record or customer token across those origins. ## 3. Create the client ```dart theme={null} import 'package:crave_storefront_sdk/crave_storefront_sdk.dart'; const apiUrl = String.fromEnvironment('CRAVEUP_API_URL'); const merchantSlug = String.fromEnvironment('CRAVEUP_MERCHANT_SLUG'); final sessionStore = InMemoryStorefrontSessionStore(); final client = CraveStorefrontClient( baseUri: Uri.parse(apiUrl), merchantSlug: merchantSlug, sessionStore: sessionStore, ); ``` This anonymous guest quickstart needs no customer-token provider. The in-memory store makes the sample runnable, but it loses the cart when the process exits. Before shipping, replace it with a `StorefrontSessionStore` backed by encrypted platform storage. ## 4. Check availability and load the menu ```dart theme={null} const locationId = String.fromEnvironment('CRAVEUP_LOCATION_ID'); Future loadTakeoutMenu() async { final readiness = await client.locations.getOrderingReadiness( locationId, fulfillmentMethod: FulfillmentMethod.takeout, ); if (readiness case OrderingUnavailable(:final reason)) { throw StateError(reason); } return client.menus.getForLocation(locationId, menuOnly: true); } ``` `getOrderingReadiness` is anonymous and side-effect free. Starting an ordering session is the point where the SDK receives and stores the guest cart capability and revision. ## 5. Start an order ```dart theme={null} final result = await client.orderingSessions.start( locationId, StartOrderingSessionRequest.fresh( fulfillmentMethod: FulfillmentMethod.takeout.wireValue, channel: OrderChannel.app, ), ); final cart = result.cart; ``` Keep one client per application session so its serialized cart lifecycle protects monotonic revisions. Dispose it with `client.close()` when the application-level owner is torn down. ## 6. Surface safe failures ```dart theme={null} try { await loadTakeoutMenu(); } on StorefrontApiException catch (error) { showApiMessage(error.code, error.requestId); } on StorefrontTimeoutException { showRetryMessage(); } ``` See the [Dart and Flutter Storefront SDK guide](/getting-started/flutter-storefront-sdk) for session storage, cancellation, conflict recovery, and security guidance. # React Native / Expo Quickstart Source: https://docs.craveup.com/quickstarts/mobile Build a mobile storefront that calls the public Crave API directly. The Storefront API is safe to call directly from an Expo app. Published catalog reads are anonymous, cart access is scoped by a short-lived capability, and signed-in customer resources use a customer JWT. No API key or mobile BFF is required. ## 1. Create the app ```bash theme={null} npx create-expo-app my-storefront --template blank-typescript cd my-storefront npx expo install expo-secure-store npm install --save-exact @craveup/storefront-sdk@2.0.1 ``` ## 2. Configure public values ```env filename=".env" theme={null} EXPO_PUBLIC_CRAVEUP_API_URL=https://api.craveup.com EXPO_PUBLIC_CRAVEUP_LOCATION_ID=loc_your_location_id EXPO_PUBLIC_CRAVEUP_MERCHANT_SLUG=your-restaurant-slug ``` Never put Crave admin, partner, or payment-provider secrets in an `EXPO_PUBLIC_` variable. ## 3. Create the client ```ts filename="src/lib/storefront.ts" theme={null} import * as SecureStore from "expo-secure-store"; import { createStorefrontClient, type StorefrontCartSession, type StorefrontSessionStore, } from "@craveup/storefront-sdk"; const apiUrl = process.env.EXPO_PUBLIC_CRAVEUP_API_URL; const merchantSlug = process.env.EXPO_PUBLIC_CRAVEUP_MERCHANT_SLUG; if (!apiUrl || !merchantSlug) throw new Error("Missing Storefront configuration"); const apiOrigin = new URL(apiUrl).origin; const secureKeyPart = (value: string) => Array.from(value, (character) => character.codePointAt(0)!.toString(16).padStart(6, "0"), ).join(""); const environmentNamespace = secureKeyPart(apiOrigin); const merchantNamespace = secureKeyPart(merchantSlug); const key = ( kind: "cart" | "customer" | "receipt", resourceId: string, ) => [ "storefront", kind, "v1", environmentNamespace, merchantNamespace, secureKeyPart(resourceId), ].join("."); const cartKey = (locationId: string) => key("cart", locationId); const customerKey = key("customer", "jwt"); const receiptKey = (receiptId: string) => key("receipt", receiptId); async function readCartSession( locationId: string, ): Promise { const storageKey = cartKey(locationId); const raw = await SecureStore.getItemAsync(storageKey); if (!raw) return null; try { const value = JSON.parse(raw) as Partial; if ( value.locationId !== locationId || typeof value.cartId !== "string" || typeof value.revision !== "number" || !Number.isInteger(value.revision) || value.revision < 0 || (value.accessToken !== undefined && typeof value.accessToken !== "string") ) { throw new Error("Invalid cart session"); } return value as StorefrontCartSession; } catch { await SecureStore.deleteItemAsync(storageKey); return null; } } export const sessionStore: StorefrontSessionStore = { async get(locationId) { return readCartSession(locationId); }, async set(session) { await SecureStore.setItemAsync( cartKey(session.locationId), JSON.stringify(session), ); }, async clear(locationId) { await SecureStore.deleteItemAsync(cartKey(locationId)); }, }; export const customerTokenStore = { get: () => SecureStore.getItemAsync(customerKey), set: (token: string) => SecureStore.setItemAsync(customerKey, token), clear: () => SecureStore.deleteItemAsync(customerKey), }; export const receiptTokenStore = { get: (receiptId: string) => SecureStore.getItemAsync(receiptKey(receiptId)), set: (receiptId: string, token: string) => SecureStore.setItemAsync(receiptKey(receiptId), token), clear: (receiptId: string) => SecureStore.deleteItemAsync(receiptKey(receiptId)), }; export const storefront = createStorefrontClient({ baseUrl: apiOrigin, sessionStore, getAuthToken: customerTokenStore.get, }); ``` ## 4. Load the merchant and create a cart ```ts theme={null} const merchant = await storefront.merchant.getBySlug( process.env.EXPO_PUBLIC_CRAVEUP_MERCHANT_SLUG!, ); const locationId = process.env.EXPO_PUBLIC_CRAVEUP_LOCATION_ID!; const session = await storefront.orderingSessions.start(locationId, { marketplaceId: "mobile", fulfillmentMethod: "takeout", }); await storefront.cart.addItem(locationId, session.cart.id, { productId: "prod_margherita", quantity: 1, selections: [], itemUnavailableAction: "remove_item", }); ``` SecureStore keys accept only alphanumeric characters, `.`, `-`, and `_`; the example encodes every dynamic component before composing dot-delimited keys. The environment, merchant, and location scopes prevent credentials from crossing applications or deployment tiers. Store only the small cart session, merchant-scoped customer JWT, and receipt capability there—not cart contents or API caches. Clear the guest cart capability after claim, deletion, expiry, or terminal checkout handling; retain only the returned revision when a claimed cart remains customer-authenticated. Call `customerTokenStore.clear()` on logout. Store a deep-link receipt capability with `receiptTokenStore.set(receiptId, token)` only when resume support requires it, and remove it after terminal display or expiry. If the API returns `CART_CONFLICT`, fetch the latest cart before asking the customer to retry. Native requests normally send no browser `Origin` header, so they do not require CORS registration. If you also ship an Expo web build, register that exact browser origin with Crave; never use a wildcard workaround. `marketplaceId` is optional attribution for the app source, such as `"mobile"`; it is not a location ID. The route already scopes the cart to `locationId`. For a signed-in shopper who still has a guest-capability cart, set the named `includeCustomerContext` option to `true` on discount or payment-session calls that need customer-aware validation or provider customer association. Omit it for guest checkout; a claimed cart uses the customer JWT automatically. ## Next steps Learn the capability and revision lifecycle. Integrate payment and authoritative order-result polling. # Next.js Quickstart Source: https://docs.craveup.com/quickstarts/nextjs Build a direct, capability-safe storefront with Next.js and the Crave Storefront SDK. This quickstart connects a Next.js App Router project directly to the public Storefront API. No BFF or browser API key is required. ## Prerequisites * Node.js 20+ * A published merchant slug and location ID * The exact browser origin for local, preview, and production deployments registered with Crave * A Stripe publishable key when you add checkout ## 1. Create the app and install the SDK ```bash theme={null} npx create-next-app@latest my-storefront --typescript --app --tailwind cd my-storefront pnpm add --save-exact @craveup/storefront-sdk@2.0.1 ``` ## 2. Configure the public origin The SDK requires an explicit `baseUrl`; it never derives an origin from a credential. ## 3. Create the browser client and scoped stores ```ts filename="src/lib/storefront-browser.ts" theme={null} "use client"; import type { StorefrontCartSession, StorefrontSessionStore, } from "@craveup/storefront-sdk"; import { createStorefrontClient } from "@craveup/storefront-sdk"; const apiUrl = process.env.NEXT_PUBLIC_CRAVEUP_API_URL; const merchantSlug = process.env.NEXT_PUBLIC_CRAVEUP_MERCHANT_SLUG; if (!apiUrl || !merchantSlug) throw new Error("Missing Storefront configuration"); const apiOrigin = new URL(apiUrl).origin; const storageKeyPart = (value: string) => encodeURIComponent(value).replaceAll(".", "%2E"); const environmentNamespace = storageKeyPart(apiOrigin); const scope = `${environmentNamespace}.${storageKeyPart(merchantSlug)}`; const key = (kind: "cart" | "customer" | "receipt", resourceId: string) => `storefront.${kind}.v1.${scope}.${storageKeyPart(resourceId)}`; const cartKey = (locationId: string) => key("cart", locationId); const customerKey = key("customer", "jwt"); const receiptKey = (receiptId: string) => key("receipt", receiptId); function readCartSession(locationId: string): StorefrontCartSession | null { const storageKey = cartKey(locationId); const raw = sessionStorage.getItem(storageKey); if (!raw) return null; try { const value = JSON.parse(raw) as Partial; if ( value.locationId !== locationId || typeof value.cartId !== "string" || typeof value.revision !== "number" || !Number.isInteger(value.revision) || value.revision < 0 || (value.accessToken !== undefined && typeof value.accessToken !== "string") ) { throw new Error("Invalid cart session"); } return value as StorefrontCartSession; } catch { sessionStorage.removeItem(storageKey); return null; } } export const sessionStore: StorefrontSessionStore = { get(locationId) { return readCartSession(locationId); }, set(session) { sessionStorage.setItem(cartKey(session.locationId), JSON.stringify(session)); }, clear(locationId) { sessionStorage.removeItem(cartKey(locationId)); }, }; export const customerTokenStore = { get: () => sessionStorage.getItem(customerKey), set: (token: string) => sessionStorage.setItem(customerKey, token), clear: () => sessionStorage.removeItem(customerKey), }; export const receiptTokenStore = { get: (receiptId: string) => sessionStorage.getItem(receiptKey(receiptId)), set: (receiptId: string, token: string) => sessionStorage.setItem(receiptKey(receiptId), token), clear: (receiptId: string) => sessionStorage.removeItem(receiptKey(receiptId)), }; export const storefront = createStorefrontClient({ baseUrl: apiOrigin, sessionStore, getAuthToken: customerTokenStore.get, }); ``` The environment, merchant, and location scopes prevent a staging token or one merchant's token from being reused by another storefront. Persist the JWT returned by `customer.verifyOtp` with `customerTokenStore.set(token)`. Capture a receipt capability from the URL fragment, remove the fragment immediately, and keep it with `receiptTokenStore.set(receiptId, token)` only while the receipt is needed. ## 4. Create a separate server client ```ts filename="src/lib/storefront-server.ts" theme={null} import { createStorefrontClient } from "@craveup/storefront-sdk"; export const storefrontServer = createStorefrontClient({ baseUrl: process.env.NEXT_PUBLIC_CRAVEUP_API_URL!, }); ``` Use the server client only for anonymous published merchant, location, menu, product, distance, order-time, and gratuity calls. It deliberately has no `sessionStore` or `getAuthToken`. Never import the browser client into a Server Component, Route Handler, or Server Action. ## 5. Start a cart and mutate it ```tsx filename="src/app/order/page.tsx" theme={null} "use client"; import { useEffect, useState } from "react"; import type { StorefrontCart } from "@craveup/storefront-sdk"; import { storefront } from "@/lib/storefront-browser"; const locationId = process.env.NEXT_PUBLIC_CRAVEUP_LOCATION_ID!; export default function OrderPage() { const [cart, setCart] = useState(null); useEffect(() => { void storefront.orderingSessions .start(locationId, { fulfillmentMethod: "takeout" }) .then(({ cart }) => setCart(cart)); }, []); async function addItem(productId: string) { if (!cart) return; setCart( await storefront.cart.addItem(locationId, cart.id, { productId, quantity: 1, selections: [], itemUnavailableAction: "remove_item", }), ); } return (

{cart ? `${cart.totalQuantity} items` : "Starting cart…"}

); } ``` The SDK stores the returned cart capability, sends it only in a header, updates the saved revision from `ETag`, and generates idempotency keys. A `CART_CONFLICT` is surfaced to your UI; reload the cart before asking the shopper to retry. Clear the guest capability after claim, deletion, expiry, or terminal checkout handling; clear the customer JWT on logout and receipt capabilities after terminal display. ## 6. Complete checkout safely For a signed-in shopper who still has a guest-capability cart, opt into the customer context when discount validation or provider customer association needs it: ```ts theme={null} const paymentSession = await storefront.checkout.createPaymentSession( locationId, cart.id, { includeCustomerContext: true }, ); ``` Omit `includeCustomerContext` for a guest checkout. A claimed cart already uses the customer JWT because its guest capability has been cleared. Confirm the Render the payment provider named by the returned session, then poll `storefront.checkout.getOrderResult(locationId, cart.id)`. Handle all four states: `payment_pending`, `order_pending`, `completed`, and `failed`. Clear the cart session only after terminal handling. ## 7. Register every browser origin Register the exact origin for local development, each stable preview, and production before testing browser calls. An origin includes the scheme, hostname, and any non-default port. Do not request a wildcard and do not send a cart capability or customer JWT while working around a CORS failure. See [Deployment](/guides/deployment). ## Next steps Add fulfillment, discounts, gratuity, and conflict handling. Confirm payment and poll the authoritative order result. Review the complete typed client surface. Configure exact origins and production environment variables. # React Quickstart Source: https://docs.craveup.com/quickstarts/react Build a direct-to-Crave storefront with React, Vite, and the Storefront SDK. Build a browser storefront with React and `@craveup/storefront-sdk`. Public catalog reads are anonymous. Cart mutations use the short-lived capability returned when the cart is created; no API key or BFF is required. ## Prerequisites * Node.js 20+ and a package manager * A published Crave merchant and location * The public Crave API origin for your environment * The exact deployed browser origin registered with Crave ## 1. Create the app and install the SDK ```bash theme={null} npm create vite@latest my-storefront -- --template react-ts cd my-storefront npm install --save-exact @craveup/storefront-sdk@2.0.1 ``` ## 2. Configure public identifiers ```env filename=".env" theme={null} VITE_CRAVEUP_API_URL=https://api.craveup.com VITE_LOCATION_ID=loc_your_location_id VITE_MERCHANT_SLUG=your-restaurant-slug ``` These values identify a public API and published resources. Do not put Crave admin, partner, payment-provider, or integration secrets in a `VITE_` variable. ## 3. Create a tab-scoped session store ```ts filename="src/lib/storefront.ts" theme={null} import { createStorefrontClient, type StorefrontCartSession, type StorefrontSessionStore, } from "@craveup/storefront-sdk"; const apiUrl = import.meta.env.VITE_CRAVEUP_API_URL; const merchantSlug = import.meta.env.VITE_MERCHANT_SLUG; if (!apiUrl || !merchantSlug) throw new Error("Missing Storefront configuration"); const apiOrigin = new URL(apiUrl).origin; const storageKeyPart = (value: string) => encodeURIComponent(value).replaceAll(".", "%2E"); const environmentNamespace = storageKeyPart(apiOrigin); const scope = `${environmentNamespace}.${storageKeyPart(merchantSlug)}`; const storageKey = ( kind: "cart" | "customer" | "receipt", resourceId: string, ) => `storefront.${kind}.v1.${scope}.${storageKeyPart(resourceId)}`; const cartKey = (locationId: string) => storageKey("cart", locationId); const customerKey = storageKey("customer", "jwt"); const receiptKey = (receiptId: string) => storageKey("receipt", receiptId); function readCartSession(locationId: string): StorefrontCartSession | null { const key = cartKey(locationId); const raw = sessionStorage.getItem(key); if (!raw) return null; try { const value = JSON.parse(raw) as Partial; if ( value.locationId !== locationId || typeof value.cartId !== "string" || typeof value.revision !== "number" || !Number.isInteger(value.revision) || value.revision < 0 || (value.accessToken !== undefined && typeof value.accessToken !== "string") ) { throw new Error("Invalid cart session"); } return value as StorefrontCartSession; } catch { sessionStorage.removeItem(key); return null; } } export const sessionStore: StorefrontSessionStore = { get(locationId) { return readCartSession(locationId); }, set(session) { sessionStorage.setItem( cartKey(session.locationId), JSON.stringify(session), ); }, clear(locationId) { sessionStorage.removeItem(cartKey(locationId)); }, }; export const customerTokenStore = { get: () => sessionStorage.getItem(customerKey), set: (token: string) => sessionStorage.setItem(customerKey, token), clear: () => sessionStorage.removeItem(customerKey), }; export const receiptTokenStore = { get: (receiptId: string) => sessionStorage.getItem(receiptKey(receiptId)), set: (receiptId: string, token: string) => sessionStorage.setItem(receiptKey(receiptId), token), clear: (receiptId: string) => sessionStorage.removeItem(receiptKey(receiptId)), }; export const storefront = createStorefrontClient({ baseUrl: apiOrigin, sessionStore, getAuthToken: customerTokenStore.get, }); ``` The SDK attaches `X-Cart-Token`, `If-Match`, and `Idempotency-Key` where required and updates the stored cart revision from response ETags. The canonical API environment, merchant, and resource scopes prevent credentials from crossing storefronts. Save the verified customer JWT with `customerTokenStore.set(token)`; save a fragment-delivered receipt capability only with `receiptTokenStore.set(receiptId, token)` and remove it after terminal display. ## 4. Fetch the published merchant ```tsx filename="src/App.tsx" theme={null} import { useEffect, useState } from "react"; import type { MerchantApiResponse } from "@craveup/storefront-sdk"; import { storefront } from "./lib/storefront"; export default function App() { const [merchant, setMerchant] = useState(null); useEffect(() => { storefront.merchant .getBySlug(import.meta.env.VITE_MERCHANT_SLUG) .then(setMerchant); }, []); if (!merchant) return

Loading...

; return

{merchant.name}

; } ``` ## 5. Start a cart and add an item ```ts theme={null} const locationId = import.meta.env.VITE_LOCATION_ID; const session = await storefront.orderingSessions.start(locationId, { marketplaceId: "web", fulfillmentMethod: "takeout", }); const cart = await storefront.cart.addItem(locationId, session.cart.id, { productId: "prod_margherita", quantity: 1, selections: [], itemUnavailableAction: "remove_item", }); ``` Keep the cart capability in tab-scoped storage. On `CART_CONFLICT`, refresh the cart and ask the user to retry; do not replay the mutation automatically. Clear the cart capability after claim, deletion, expiry, or terminal checkout handling, and clear the merchant-scoped JWT on logout. `marketplaceId` is an optional source-attribution label, such as `"web"`; it is not a location ID. The route already scopes the cart to `locationId`. ## 6. Register the browser origin Register the exact deployed origin—including scheme, hostname, and any non-default port—with Crave before testing. Register stable preview origins separately; wildcard origins are not accepted. ## Next steps Fetch the published menu and product data. Handle capabilities, revisions, and idempotent mutations. Create a payment intent and poll the authoritative order result. See the complete direct API client. # Vanilla JavaScript Quickstart Source: https://docs.craveup.com/quickstarts/vanilla-js Use the public Crave Storefront API directly from a browser without a framework. This example uses `fetch` against the public Storefront API. Catalog reads are anonymous; cart operations are protected by the capability returned with the ordering session. ## 1. Create a small client ```js filename="storefront.js" theme={null} const API_ORIGIN = "https://api.craveup.com"; const API = `${API_ORIGIN}/api/v1/storefront`; const merchantSlug = "your-restaurant-slug"; const locationId = "loc_your_location_id"; const storageKeyPart = (value) => encodeURIComponent(value).replaceAll(".", "%2E"); const environmentNamespace = storageKeyPart(new URL(API_ORIGIN).origin); const cartStorageKey = [ "crave", "storefront", "cart", "v1", environmentNamespace, storageKeyPart(merchantSlug), storageKeyPart(locationId), ].join("."); function readCartSession() { const raw = sessionStorage.getItem(cartStorageKey); if (!raw) return null; try { const value = JSON.parse(raw); if ( !value || typeof value !== "object" || typeof value.cartId !== "string" || typeof value.accessToken !== "string" || !Number.isInteger(value.revision) || value.revision < 0 ) { throw new Error("Invalid cart session"); } return value; } catch { sessionStorage.removeItem(cartStorageKey); return null; } } let cartSession = readCartSession(); function mutationHeaders() { if (!cartSession) throw new Error("Start an ordering session first."); return { "Content-Type": "application/json", "X-Cart-Token": cartSession.accessToken, "If-Match": `"cart-${cartSession.revision}"`, "Idempotency-Key": crypto.randomUUID(), }; } async function parse(response) { const body = await response.json(); if (!response.ok) throw body; const match = response.headers.get("ETag")?.match(/^(?:W\/)?"cart-(\d+)"$/); if (match && cartSession) { const nextRevision = Number(match[1]); if (nextRevision > cartSession.revision) { cartSession.revision = nextRevision; sessionStorage.setItem(cartStorageKey, JSON.stringify(cartSession)); } } return body; } ``` Before running this in a browser, register its exact origin with Crave. Scheme, hostname, and non-default port are part of the origin, so local, preview, and production deployments need separate registrations; wildcards are not accepted. ## 2. Read the published catalog ```js theme={null} const merchant = await fetch(`${API}/merchant/${merchantSlug}`).then(parse); const menu = await fetch(`${API}/locations/${locationId}/menus`).then(parse); ``` ## 3. Start an ordering session ```js theme={null} const response = await fetch( `${API}/locations/${locationId}/ordering-sessions`, { method: "POST", headers: { "Content-Type": "application/json", "Idempotency-Key": crypto.randomUUID(), }, body: JSON.stringify({ marketplaceId: "web", fulfillmentMethod: "takeout", }), }, ); const session = await parse(response); cartSession = { cartId: session.cart.id, accessToken: session.cartAccessToken, revision: session.cart.revision, }; sessionStorage.setItem(cartStorageKey, JSON.stringify(cartSession)); ``` `marketplaceId` is optional source attribution, such as `"web"`; it is not a location ID. The route already scopes the cart to `locationId`. ## 4. Add an item ```js theme={null} const cart = await fetch( `${API}/locations/${locationId}/carts/${cartSession.cartId}/items`, { method: "POST", headers: mutationHeaders(), body: JSON.stringify({ productId: "prod_margherita", quantity: 1, selections: [], itemUnavailableAction: "remove_item", }), }, ).then(parse); ``` Do not place the cart ID or capability in a query string. Clear the guest record after claim, cart deletion, expiry, or terminal checkout handling. If customer-authenticated access continues after claim, retain only the authoritative revision in a separate authenticated-cart record. On `CART_CONFLICT`, fetch the cart using `X-Cart-Token`, accept the latest ETag, and ask the user to retry instead of replaying the write. ## Next steps Understand every authentication boundary. Complete payment and poll the authoritative order result.