> ## Documentation Index
> Fetch the complete documentation index at: https://docs.craveup.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Vanilla JavaScript Quickstart

> 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

<CardGroup cols={2}>
  <Card title="REST API Essentials" icon="square-terminal" href="/getting-started/rest-api">
    Understand every authentication boundary.
  </Card>

  <Card title="Checkout Flow" icon="credit-card" href="/guides/checkout-flow">
    Complete payment and poll the authoritative order result.
  </Card>
</CardGroup>

<Snippet file="need-help.mdx" />
