Skip to main content
Managed Cloud projects use their included storefront and pinned runtime. The published SDK version and this platform source candidate do not establish compatibility with a Cloud installation. Verify the deployed API and caller pair using the Cloud release results.
@craveup/storefront-sdk@4.0.0 is published under the development dist-tag for the scoped public API at /api/v1/storefront, with an exact @craveup/commerce-contracts@4.0.0 dependency. Catalog and cart operations carry explicit menu, channel and release context; configured food comes from the server. This guide describes that published pair, including its frontend Clerk customer integration.
API-owned customer authentication is a separate SDK 5.0.0 / contracts 5.0.0 source candidate that is not yet published or activated. Do not use its new authentication methods with the published 4.0.0 pair. Published SDK 2.0.1 lacks this API’s scoped menu/cart context and its customer login/OTP/logout and direct payment/result methods target retired routes. Keep those consumers blocked until the matching deployed API, frozen installs and caller adoption are verified. Development publication does not establish production compatibility. Do not substitute an unpublished package or private transport.

Install and configure

Create separate server and browser clients. A server client has no session or JWT provider; use it only for anonymous published calls:
A browser client owns the caller-provided, tab-scoped cart store. Customer tokens come from the active Clerk session and are never persisted by the app:
Create this client only after Clerk is loaded. Supply getAuthToken from the active Clerk session as () => getToken({ template: "crave-customer" }) and resolve merchantSlug from the storefront’s trusted host or public configuration. See Customer identity for the framework adapter and activation requirements. 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: Reject malformed or unknown stored versions instead of casting unvalidated JSON. The Next.js, React, and Expo 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. Let Clerk clear its session on logout, and clear a receipt capability after terminal display or expiry. Never copy customer JWTs into application storage or put capabilities or customer JWTs in URLs, public environment variables, logs, source maps, HTML, or analytics.

Catalog and ordering

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

The Stripe client secret returned with CUSTOMER_ACTION_REQUIRED 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 order submission 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

Crave does not expose customer login, OTP, or token-storage endpoints. Use the environment-specific Customer Clerk application and make customer requests only after Clerk and the SDK token provider are ready. The SDK obtains the current short-lived token for each customer request and sends X-Crave-Merchant-Slug from getMerchantSlug. Then use customer.getProfile, customer.orders, customer.addresses, customer.savedPayments, loyalty.ledger, and loyalty.claims.

Receipts

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

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. 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.

Customer catering quotes

Catering links use an event-specific capability. Pass it only through the declared cateringToken option; never put it in a query parameter or arbitrary headers. All catering operations return private, non-cacheable responses.
Check view.actions before showing acceptance or decline. Only accept a non-null offer after the customer reviews its exact total. On CATERING_OFFER_STALE, refetch and require a new click; do not automatically accept a different amount. Reuse the same idempotency key only for the same request. The customer response excludes restaurant-only notes and contact data, shows current refundedTotal values, and provides receipt links only for paid payments belonging to this event. A refund does not reopen deposit acceptance. The OpenAPI operations are getCateringEvent, acceptCateringQuote, and declineCateringQuote, under /locations/{locationId}/catering/events/{eventId} (with /accept or /decline for POST). Missing X-Catering-Token returns 401; invalid, expired, revoked or wrong-location links return the same 404 response.

Customer catering balance

An accepted event returns balance with the agreed total, settled captures, outstanding amount, due date, and a nullable payment offer. After the customer reviews a non-null offer, pay the whole balance with:
payCateringBalance uses /balance-payment; send no amount. A successful response is checkout with dueNow and remaining, or no_balance. Refetch after checkout to confirm settlement. On a stale offer, refetch and require a new click. Pending payments, refunds, reconciliation and closed events hold online collection. Refunds never increase what is owed; REFUND_IN_REVIEW requires staff review. New customer links last until the later of the event’s 30-day expiry and 30 local calendar days after the balance-due day. Existing links are never renewed.

Catering inquiries from your website

A restaurant that turned on website inquiries accepts catering requests from its own site. Both calls are anonymous. Show the form only when inquiryForm reports enabled: true.
Create one idempotencyKey per inquiry and reuse it when retrying that inquiry. A retry then returns the same receipt instead of sending a second request. The receipt holds only the six-digit reference and the answerBy time. A location that is not accepting inquiries returns 409 CATERING_INQUIRIES_NOT_ACCEPTED. Submissions are limited to five a minute per sender. The OpenAPI operations are getCateringInquiryForm and submitCateringInquiry.

Published catering menu

A restaurant’s published catering menu is readable without login, a cart or a catering link. Only the location ID goes in the request.
Items, modifier groups and options carry their canonical IDs, the printed price (exact cents, a range, or quote_only), the price basis and servings as the restaurant reviewed them. A missing fact is null, never zero. catalogRevision names the publication the menu was read from. The response is never cached, so a menu stops being readable as soon as it is unpublished or catering is switched off; both answer 404 CATERING_MENU_UNAVAILABLE, and an unknown or archived location answers 404 NOT_FOUND. The OpenAPI operation is storefrontGetPublishedCateringMenu.

Requesting catering cancellation

client.catering.requestCancellation(locationId, eventId, { quoteId, eventRevision, reason }, { cateringToken, idempotencyKey }) records a request for restaurant review. It never cancels the service, charges a cancellation fee, or issues a refund. Use the current accepted quote and event revision from the customer response. Read the recorded request with client.catering.cancellationRequest(locationId, eventId, { cateringToken }). A revoked, expired or erased link cannot read or replay a request. Keep the same key and payload when retrying; a different key cannot replace an open request. Staff decisions and refunds are separate operations. Their controls remain unavailable until the corresponding backend and dashboard slices are deployed.