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

# Troubleshooting

> Common issues and solutions when building with the Crave API.

## Authentication

### "Missing API key" or 401 errors

**Cause:** The `X-API-Key` header is missing or the key is invalid.

**Fix:**

1. Check that you're passing the header: `X-API-Key: sk_live_...` (or `sk_test_...` for test mode)
2. Verify the key in the Dashboard under **Settings > API Keys**
3. Ensure the key hasn't been revoked
4. If using the SDK, check your environment variable: `NEXT_PUBLIC_CRAVEUP_API_KEY`

```ts theme={null}
// Verify your client is initialized correctly
const storefront = createStorefrontClient({
  apiKey: process.env.NEXT_PUBLIC_CRAVEUP_API_KEY!, // must not be undefined
});
```

### "Location mismatch" or 403 errors

**Cause:** The API key is scoped to a different location than the one in the request.

**Fix:** Each API key is tied to a specific location. Use the key generated for the location you're querying, or generate a new key for the target location.

***

## Cart & Ordering

### "Cart not found" (404)

**Cause:** The cart has expired or been deleted.

**Fix:**

* Carts expire after a period of inactivity. Start a new ordering session.
* If you're persisting `cartId` in localStorage, clear it and create a fresh session.

```ts theme={null}
// Always handle cart expiration gracefully
try {
  const cart = await storefront.cart.get(locationId, cartId);
} catch (error) {
  if (error instanceof ApiError && error.status === 404) {
    // Cart expired — start a new session
    const session = await storefront.orderingSessions.start(locationId, {
      marketplaceId: locationId,
    });
    localStorage.setItem('cartId', session.cartId!);
  }
}
```

### "Cart locked" (400)

**Cause:** A payment is being processed for this cart. Items can't be modified during payment.

**Fix:** Wait for the payment to complete or fail. If the payment fails, the cart unlocks automatically. If it succeeds, the cart transitions to `COMPLETED`.

### "Invalid modifier selection" (400)

**Cause:** The selected modifiers don't satisfy the group's `min`/`max` rules.

**Fix:**

* Check the modifier group's constraints before submitting
* Ensure the number of selected options is between `min` and `max`
* Verify that individual modifier item quantities don't exceed their `maxQuantity`

### "Location closed" (422)

**Cause:** The location is not accepting orders at the requested time.

**Fix:**

1. Fetch order times: `storefront.locations.getOrderTimes(locationId)`
2. If `requireScheduledOrders` is true, show a time picker
3. If no time slots are available, the location is closed — show a message

### "Minimum order not met" (400)

**Cause:** The cart total is below the location's minimum order amount.

**Fix:** Display the minimum amount to the customer and prompt them to add more items. The minimum is available in the location settings.

***

## Payments

### Payment intent fails to create

**Cause:** Cart is missing required data (customer details, fulfillment method, or order time).

**Fix:** Ensure all checkout steps are complete before creating a payment intent:

1. Fulfillment method is set
2. Order time is set (ASAP or scheduled)
3. Customer details are validated
4. For delivery: address is set with coordinates

### Stripe Elements not rendering

**Cause:** Stripe.js is not loaded or the `clientSecret` is invalid.

**Fix:**

1. Verify `NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY` is set
2. Pass the `stripeAccountId` from the payment intent response when initializing Stripe
3. Ensure the `clientSecret` is passed to the Elements provider

```tsx theme={null}
// Must use the restaurant's connected account
const stripePromise = loadStripe(
  process.env.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY!,
  { stripeAccount: payment.stripeAccountId } // required for Connect
);
```

### 3D Secure / SCA authentication popup not appearing

**Cause:** Missing `return_url` in the `confirmPayment` call.

**Fix:** Always provide a `return_url`:

```ts theme={null}
await stripe.confirmPayment({
  elements,
  confirmParams: {
    return_url: `${window.location.origin}/order/confirmation`,
  },
});
```

***

## Fulfillment

### "Delivery out of range" (422)

**Cause:** The customer's address is outside the location's delivery zone.

**Fix:**

1. Check distance before setting delivery: `storefront.locations.getDistance(locationId, { lat, lng, unit: 'miles' })`
2. Compare against the location's delivery radius
3. Show a clear message if out of range

### Table-side / room service not available

**Cause:** The fulfillment method is not enabled for the location.

**Fix:** Check `location.methodsStatus` and only show enabled methods:

```ts theme={null}
const merchant = await storefront.merchant.getBySlug('my-restaurant');
const methods = merchant.locations[0].methodsStatus;
// { pickup: true, table: true, delivery: false, roomService: false }
```

***

## Webhooks

### Webhooks not being received

**Check:**

1. Your endpoint URL is correct and publicly accessible
2. Your server returns `200` within 10 seconds
3. The endpoint isn't behind authentication (webhook requests don't include session cookies)
4. Check the delivery log in Dashboard under **Settings > Webhooks** for failed attempts

### Signature verification failing

**Cause:** The `CRAVEUP_WEBHOOK_SECRET` doesn't match, or the body is being parsed before verification.

**Fix:**

* Use the raw request body for signature verification (not the parsed JSON)
* Ensure the secret matches the one shown in the Dashboard

```ts theme={null}
// Must use raw body, not parsed JSON
const body = await request.text(); // not request.json()
const expected = createHmac('sha256', WEBHOOK_SECRET).update(body).digest('hex');
```

***

## SDK

### "TypeError: storefront.X is not a function"

**Cause:** Incorrect SDK version or wrong import.

**Fix:**

1. Update to the latest SDK: `pnpm add @craveup/storefront-sdk@latest`
2. Verify the import: `import { createStorefrontClient } from '@craveup/storefront-sdk'`
3. Check that you're calling methods on the correct namespace (e.g., `storefront.cart.get()` not `storefront.get()`)

### SDK requests failing in Server Components

**Cause:** The SDK uses `fetch` internally. In Next.js Server Components, ensure your API key is available server-side.

**Fix:** Use a server-side environment variable (without `NEXT_PUBLIC_` prefix) or ensure the public variable is accessible in the server context.

***

## Build & Deployment

### Next.js build fails with SDK type errors

**Fix:** Ensure your `tsconfig.json` includes `"moduleResolution": "bundler"` or `"node"` and that the SDK is installed (not just a peer dependency).

### Images not loading in production

**Cause:** Next.js Image Optimization requires configured domains.

**Fix:** Add the Crave CDN domain to your `next.config.ts`:

```ts filename="next.config.ts" theme={null}
const nextConfig = {
  images: {
    remotePatterns: [
      { protocol: 'https', hostname: '*.craveup.com' },
      { protocol: 'https', hostname: 'res.cloudinary.com' },
    ],
  },
};
```

***

## Still stuck?

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