@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.
Install and configure
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.startreturns a purpose-limited cart capability and saves it throughsessionStore.- 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
receiptTokenand sends it only asX-Receipt-Token.
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
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
X-Crave-Merchant-Slug
from getMerchantSlug. Then use customer.getProfile, customer.orders,
customer.addresses, customer.savedPayments, loyalty.ledger, and
loyalty.claims.
Receipts
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.
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 browserOrigin header, but an Expo web deployment still needs registration.
Customer catering quotes
Catering links use an event-specific capability. Pass it only through the declaredcateringToken option; never put it in a query parameter or arbitrary headers.
All catering operations return private, non-cacheable responses.
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 returnsbalance 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 wheninquiryForm reports
enabled: true.
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.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.