# Overview (https://docs.salla.dev/twilight/react/api)



Read this first when your theme needs store data: products, the cart, orders, settings. Everything comes from the store API, and the engine puts one HTTP client in front of it.

<Callout title="Framework: TanStack Start">
  The loader examples use TanStack Start, the default and currently supported framework. [See what depends on the framework](https://docs.salla.dev/twilight/react/start/frameworks).
</Callout>

## Three layers [#three-layers]

```text
queries      cart.queries.detail(id)     TanStack Query options for components (cache, Suspense)
modules      cart.get(id), product.find  plain async functions with typed results
client       api                         base URL, locale, store, token, scope, timeout, retries
```

1. The **client** (`api`) adds the locale, store, login token and branch to every request.
2. **Modules** wrap one area each (`cart`, `product`, `order` and so on) as plain `async` functions. See [all modules](https://docs.salla.dev/twilight/react/api/modules).
3. **`queries`** on a module are TanStack Query `queryOptions`, ready for `useQuery` in a component.

Pick the highest layer that does the job. Each one is built on the one below.

## Make a request [#make-a-request]

Start with a module. Import it from its own subpath:

```ts
import { cart } from '@salla.sa/twilight-theme-engine/api/cart';
import { product } from '@salla.sa/twilight-theme-engine/api/product';
import { order } from '@salla.sa/twilight-theme-engine/api/order';

const item = await product.find('123');                    // one product, with details
const latest = await product.list({ source: 'latest' });   // { items, next, filters }
const myCart = await cart.get(cartId);                     // cartId is a number
const orders = await order.list({ status: 'completed' });  // { data, cursor }
```

Parameters and results are typed, so your editor shows what each function accepts.

### In a route loader [#in-a-route-loader]

A loader runs before the page renders, on the server for the first visit. Fetch through the query client so the result is cached and sent to the browser with the page (see [Loading data](https://docs.salla.dev/twilight/react/guide/loading-data)):

```ts title="app/routes/sale.tsx"
import { getTwilightContext } from '@salla.sa/twilight-theme-engine/tanstack';
import { product } from '@salla.sa/twilight-theme-engine/api/product';

loader: () => {
  const { queryClient } = getTwilightContext();
  return queryClient.fetchQuery(product.queries.list({ source: 'offers' }));
},
```

Calling `product.list(...)` directly also works; you just skip the cache. Many modules also have a `...OrThrow` variant (`product.findOrThrow`) that turns a missing resource (404 or 410) into a not-found page. See [Errors, timeouts and retries](https://docs.salla.dev/twilight/react/api/errors-timeouts-and-retries).

### In a component [#in-a-component]

Use the module's `queries` with TanStack Query. It refetches, deduplicates and cancels for you:

```tsx title="app/components/CategoryProducts.tsx"
import { useQuery } from '@tanstack/react-query';
import { product } from '@salla.sa/twilight-theme-engine/api/product';

export function CategoryProducts({ categoryId }: { categoryId?: number }) {
  const { data, isError } = useQuery({
    ...product.queries.list({ source: 'categories', sourceValue: [categoryId!] }),
    enabled: categoryId !== undefined,
  });

  if (isError) return <p>Could not load products.</p>;
  return (
    <ul>
      {data?.items.map((item) => (
        <li key={item.id}>{item.name}</li>
      ))}
    </ul>
  );
}
```

Spread the options so you can add your own (`enabled`, `select`, `staleTime`). Use `useSuspenseQuery` the same way when a parent `<Suspense>` boundary should show the loading state: `data` is then never undefined, and a failure goes to the nearest error boundary.

After something changes, refresh the cached copy with the module's own key:

```ts
queryClient.invalidateQueries({ queryKey: cart.queries.detail(cartId).queryKey });
```

### Paged lists [#paged-lists]

Lists use cursors. A page is `{ items, next }`: `next` is the cursor of the following page, or `null` on the last one. Treat the cursor as an opaque string.

```ts
const first = await product.list({ source: 'latest', perPage: 16 });
if (first.next) {
  const second = await product.list({ source: 'latest', cursor: first.next });
}
```

Not every module pages the same way. `wishlist`, `wallet` and `brand.list` take a page number, and `order.list` returns `{ data, cursor }`. Each module page says which.

### An endpoint with no module [#an-endpoint-with-no-module]

Use `api` directly. It already has the base URL, so pass relative paths:

```ts
import { api } from '@salla.sa/twilight-theme-engine/api/client';

// The paths are illustrative; use your own endpoint.
const { data } = await api.get('cart/42').json<{ data: Cart }>();

await api.post('some/endpoint', { json: { name: 'value' } });

const res = await api.get('products', {
  searchParams: { source: 'latest', per_page: 8 },
  headers: { 'x-trace': 'home' },   // merged with the client's own headers
  timeout: 3000,                    // overrides the 8 s default for this call
  signal,                           // an AbortSignal
}).json();
```

`.json<T>()` parses the body into a typed result. If you use the endpoint in more than one place, [wrap it in a module](https://docs.salla.dev/twilight/react/api/write-your-own-endpoint).

### Server and browser [#server-and-browser]

The same code runs in both places. On the server the client reads the token from the request cookie; in the browser from the cookie or the Salla SDK storage. You do not branch on it. Keep browser-only code (`window`, `localStorage`) out of anything a loader or `queryFn` runs, because that code also runs on the server.

## Why every request goes through the client [#why-every-request-goes-through-the-client]

A bare `fetch` looks fine:

```ts title="app/lib/bad-example.ts"
const res = await fetch('https://api.salla.dev/store/v1/products?source=latest');
const { data } = await res.json();
```

It works in a demo store, for an anonymous customer, in one language. But the request does not know which store it is for, which language the page uses, who the customer is or which branch they picked. The API still answers, so nothing throws. You just get the wrong data:

* **Wrong or missing store.** You get an error, or data for a different store.
* **Wrong language.** Names and descriptions come back in the API default.
* **Guest instead of the customer.** Orders and wishlist fail with `token_not_provided`, or show guest data to a signed-in customer.
* **Wrong branch.** Stock and prices are resolved per branch, so you see the market, not the branch the customer chose.
* **No timeout or retries.** A slow backend hangs the render instead of failing after 8 seconds.

The same call through the client:

```ts title="app/lib/good-example.ts"
import { api } from '@salla.sa/twilight-theme-engine/api/client';

const { data } = await api
  .get('products', { searchParams: { source: 'latest' } })
  .json<{ data: unknown }>();
```

| Concern           | Bare `fetch`                                  | `api.get(...)`                                             |
| ----------------- | --------------------------------------------- | ---------------------------------------------------------- |
| Base URL          | You repeat it                                 | Set once, relative paths                                   |
| Store             | You find and send it                          | Sent automatically, overridable per request                |
| Language          | Missing                                       | `accept-language` set to the current locale                |
| Customer          | You read the token on both server and browser | `Authorization: Bearer` added when a token exists          |
| Branch and market | Missing                                       | `s-scope-*` headers when a scope is active                 |
| Theme version     | Missing                                       | Version header when one applies                            |
| Currency          | Missing                                       | `currency` header when the customer chose another currency |
| Timeout           | None                                          | 8 seconds (5 for `cdn`)                                    |
| Retries           | None                                          | Safe methods retried twice with backoff                    |
| Errors            | Check `res.ok` yourself                       | Non-2xx throws an `HTTPError`                              |

### What the client does not do [#what-the-client-does-not-do]

* **No caching.** Caching and refetching are TanStack Query's job. See [Loading data](https://docs.salla.dev/twilight/react/guide/loading-data).
* **No query keys.** If a response varies by scope, locale or version, put those in the key yourself. The engine's modules already do this where it matters; each module page lists its keys.
* **One base URL.** It is `https://api.salla.dev/store/v1` unless `VITE_API_URL` names another backend; see [Base URL](https://docs.salla.dev/twilight/react/api/client#base-url).
* **Not for third-party URLs.** Use whatever you like there, and never send the client's headers to a third party.

## Next [#next]

* [Client reference](https://docs.salla.dev/twilight/react/api/client): the exports, headers, timeout and retry numbers.
* [Authentication and scope](https://docs.salla.dev/twilight/react/api/authentication-and-scope): who the request is for.
* [Errors, timeouts and retries](https://docs.salla.dev/twilight/react/api/errors-timeouts-and-retries): handling failure.
* [Write your own endpoint](https://docs.salla.dev/twilight/react/api/write-your-own-endpoint): a typed module on top of `api`.
* [Modules](https://docs.salla.dev/twilight/react/api/modules): one page per area, with every method.
