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

# Credentials & channels

> Get your Store ID and Storefront API key, and understand the store, channel, and API key model that determines which products your storefront can see.

export const AdminPortalLink = ({path = "/organisation/apikeys"}) => {
  const STORAGE_KEY = "ce-docs:admin-subdomain";
  const TLDS = ["commercengine.io", "commercengine.dev"];
  const [subdomain, setSubdomain] = useState("");
  const [tld, setTld] = useState(TLDS[0]);
  useEffect(() => {
    try {
      const saved = window.localStorage.getItem(STORAGE_KEY);
      if (saved) setSubdomain(saved);
    } catch {}
  }, []);
  const sanitize = value => value.trim().toLowerCase().replace(/^https?:\/\//, "").split("/")[0].replace(/\.commercengine\.(io|dev)$/, "").replace(/[^a-z0-9-]/g, "");
  const clean = sanitize(subdomain);
  const href = clean ? `https://${clean}.${tld}${path}` : null;
  const remember = value => {
    setSubdomain(value);
    try {
      window.localStorage.setItem(STORAGE_KEY, sanitize(value));
    } catch {}
  };
  return <div className="ce-portal">
      <label className="ce-portal-label" htmlFor="ce-portal-subdomain">
        Your Commerce Engine subdomain
      </label>

      <div className="ce-portal-row">
        <div className="ce-portal-field">
          <input id="ce-portal-subdomain" type="text" inputMode="url" autoComplete="off" spellCheck="false" placeholder="your-org" value={subdomain} onChange={e => remember(e.target.value)} onKeyDown={e => {
    if (e.key === "Enter" && href) window.open(href, "_blank", "noopener");
  }} />
          <select aria-label="Domain" value={tld} onChange={e => setTld(e.target.value)}>
            {TLDS.map(t => <option key={t} value={t}>
                .{t}
              </option>)}
          </select>
        </div>

        <a className="ce-btn ce-btn--primary" href={href ?? undefined} target="_blank" rel="noopener noreferrer" aria-disabled={!href} data-disabled={!href}>
          Open API keys
          <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
            <path d="M7 17 17 7M9 7h8v8" />
          </svg>
        </a>
      </div>

      <p className="ce-portal-url" aria-live="polite">
        {href ? <>
            Opens <code>{href}</code>
          </> : <>Enter your subdomain to build the link. Both <code>.io</code> and <code>.dev</code> work identically.</>}
      </p>
    </div>;
};

Every Commerce Engine storefront needs two values: a **Store ID** and a **publishable Storefront API key**. Both come from the Admin Portal.

## Get your keys

Each organisation has its own Admin Portal subdomain, so enter yours to open the API keys page directly.

<AdminPortalLink />

<Note>
  The link opens the API keys page if you are already signed in, and the login screen if you are not. Both `commercengine.io` and `commercengine.dev` resolve to the same portal.
</Note>

## What signup already created

When you signed up, Commerce Engine provisioned a working set for you:

| Created                | What it is                                                     |
| ---------------------- | -------------------------------------------------------------- |
| One store              | Your commerce workspace — catalog, customers, orders, settings |
| One default channel    | A selling surface of type **Web**                              |
| One Storefront API key | Scoped to that default channel                                 |

For a single web storefront, that default set is all you need. Copy the Store ID and the Storefront API key into your environment variables and continue with the [Quickstart](/docs/storefront/quickstart).

<Warning>
  The Storefront API key is publishable and intended for storefront clients. It is **not** an Admin API key and not a payment-provider credential. Never put admin or payment secrets in browser-visible environment variables.
</Warning>

## Stores, channels, and keys

This is the part worth understanding before you build, because it determines which products your storefront can see.

```
Store
 └─ Channel (Web)          ← products are assigned to channels
     └─ Storefront API key ← keys are scoped to one channel
```

Three rules follow from that shape:

1. **Products are linked to channels.** A product that is not assigned to a channel does not exist as far as that channel is concerned.
2. **API keys are scoped to one channel.** The key you use decides which slice of the catalog you can read.
3. **A store can have several channels.** Each gets its own key.

### Channel types

| Type          | Used for                                                               |
| ------------- | ---------------------------------------------------------------------- |
| `Web`         | Browser storefronts — the default, and what the Storefront SDK targets |
| `App`         | Native mobile apps                                                     |
| `PoS`         | In-store selling — see [POS](/docs/pos/overview)                            |
| `Marketplace` | Multi-seller catalogs. Advanced; most stores never need it             |

This is why POS has its own SDK and its own authentication model, and why the API reference has a separate marketplace catalog: they are different channels, not different stores.

## If your catalog comes back empty

An API key scoped to the wrong channel does not return an error. It returns an empty list.

```typescript theme={"theme":{"light":"github-light","dark":"github-dark"}}
const { data, error } = await publicSdk.catalog.listProducts();
// error is null, data.products is []
```

That is a valid response — the channel genuinely has no products visible to it. If you see this and expect products, check in order:

<Steps>
  <Step title="Confirm the key's channel">
    In the Admin Portal, open the API key and note which channel it belongs to.
  </Step>

  <Step title="Confirm the products are assigned to that channel">
    A product visible in the Admin Portal is not automatically visible to every channel.
  </Step>

  <Step title="Confirm the environment matches">
    Staging and production have separate data. A staging key against a production store ID returns nothing useful.
  </Step>
</Steps>

<Tip>
  Empty results with `error === null` almost always mean channel scoping, not a broken integration. Reach for the Admin Portal before debugging the SDK.
</Tip>

## Environments

| Environment | Base URL                                                            |
| ----------- | ------------------------------------------------------------------- |
| Staging     | `https://staging.api.commercengine.io/api/v1/{store_id}/storefront` |
| Production  | `https://prod.api.commercengine.io/api/v1/{store_id}/storefront`    |

The SDK selects the base URL from the `environment` option, so you set it once in configuration rather than per call. See [Configuration](/docs/sdk/configuration).

## Next steps

<CardGroup cols={2}>
  <Card title="Quickstart" icon="rocket" href="/docs/storefront/quickstart">
    Install the SDK, set environment variables, and make your first catalog call.
  </Card>

  <Card title="Configuration" icon="gear" href="/docs/sdk/configuration">
    Every SDK option, including environment and token storage selection.
  </Card>
</CardGroup>
