---
title: "Keys and CORS"
description: "Public and secret storefront keys, registered origins and CORS behaviour."
---

> Documentation Index
> Fetch the complete documentation index at: https://developer.unipage.be/llms.txt
> Use this file to discover all available pages before exploring further.

# Keys and CORS

You get two keys per storefront, both requested from Unipage. Together they
are one storefront, with a name, its registered origins and optionally an
[order webhook](/storefront/webhooks/). The public key's origins are where
your shoppers may browse from and return to after paying. The restaurant is
resolved from the key: there is no restaurant id in the URL.

| | Public key | Secret key |
|---|---|---|
| Looks like | `upg_sf_pk_...` | `upg_sf_sk_...` |
| Where it lives | in your browser bundle, safe to expose | only on your server, never in a browser |
| Sent as | `X-Unipage-Key: <key>` header | `Authorization: Bearer <key>` header |
| Grants | the read and quote routes | every route, including orders |
| Bound to | the origins you registered (`https://shop.example.com`) | nothing: server-to-server only |

### Public key (browser)

```js
const res = await fetch("https://api.unipage.eu/storefront/v1/menu?orderType=TAKEOUT", {
  headers: { "X-Unipage-Key": import.meta.env.VITE_UNIPAGE_PUBLIC_KEY },
});
```

### Secret key (server)

```sh
curl https://api.unipage.eu/storefront/v1/orders/q4m8z \
  -H "Authorization: Bearer $UNIPAGE_SECRET_KEY"
```

A Core API key (`upg_capi_...`, the restaurant's own back-office key) is also
accepted where the secret key is, but give a storefront its own keys.

## Requesting keys

When you request keys, give every origin your shop is served from
(`scheme://host[:port]`, no path, no trailing slash).

Local development needs no registration: every public key also accepts
`localhost`, `127.0.0.1` and `[::1]` on any port, over http or https. A
storefront on your machine that uses production keys places real orders, so
test against a test restaurant.

> **Caution**
>
> Both keys are shown to you once. Unipage only keeps a hash of the secret
> key and cannot show it again.

Origins can be changed later without a new key: the public key stays the
same, so your storefront needs no new build.

## Rotating a key

If a key leaks, ask Unipage to rotate it. Each key is rotated on its own: the
old one stops working at once and the new one is shown to you once.

- **Secret key**: only your server needs the new key.
- **Public key**: it keeps its origins and webhook, but it is in your browser
  bundle, so rebuild and deploy your storefront with the new one.

## Authentication errors

- A missing, unknown or revoked key answers `401 UNAUTHENTICATED`.
- A public key on a secret route, or used from an origin that is not
  registered for it (and is not local), answers `403 FORBIDDEN`.
- A request without an `Origin` header (curl, a server) is accepted with a
  public key: the origin list restricts browsers, the key identifies you.

## CORS

A browser preflight (`OPTIONS`) is answered `204` for a registered or local origin, with:

```http
Access-Control-Allow-Headers: X-Unipage-Key, Content-Type
Access-Control-Allow-Methods: GET, POST, OPTIONS
Access-Control-Max-Age: 600
```

Every response (errors and 429 included) carries `Vary: Origin` and, for a
registered or local origin:

```http
Access-Control-Allow-Origin: <origin>
Access-Control-Expose-Headers: Retry-After
```

The secret routes send no CORS headers for a browser: call them from your
server.

Source: https://developer.unipage.be/storefront/keys/index.mdx
