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

# Getting your API keys

> Create an API key in the Laces app, copy the one-time secret, choose permissions and domains, and regenerate or revoke it later.

API keys are self-serve, in the Laces app. You need to be an **owner or
admin** of the organization; other members don't see the Developers entry.

There is one environment. The keys you create in the app are **production
keys**: they work against `https://connect.laces.run/v1` and take real
payments. To build and test safely, see
[Testing your integration](/concepts/testing).

## What you get

One **API key** in the app is one API client, and it has three values:

| Value | Example | Secret? | Use it |
| - | - | - | - |
| **Client ID** | `LCSCLI-EXAMPLECLNT2345EXAMP` | No | On your server, with the secret, to get tokens |
| **Secret** (client secret) | `LCSSECK-EXAMPLE…SECRE-X` | **Yes. Shown once.** | On your server only |
| **Publishable key** | `LCSPUBK-ABCDEFGHJKLMNPQRSTUVWXYZ23456789-X` | No | In your web pages |

<Info>
  Every key, token and id on this site is a made-up example value. Your real
  keys are issued to you in the Laces app, under **Settings → Developers**.
</Info>

## Create a key

<Steps>
  <Step title="Open Developers">
    In the Laces app, go to **Settings** and scroll to the bottom. Tap
    **Developers** ("API keys, webhooks and integrations"). Only owners and
    admins see this tile.

    <Frame caption="Settings, Get help section, with the Developers tile">
      <img src="https://mintcdn.com/laces/TvNmP5tvH8tqleQI/images/app/settings-developers-tile.png?fit=max&auto=format&n=TvNmP5tvH8tqleQI&q=85&s=3d7a5800a8fdf080ca4be78062bb260a" alt="The Laces app Settings screen scrolled to the Get help section, with the Developers tile (API keys, webhooks and integrations) below it" width="1206" height="1800" data-path="images/app/settings-developers-tile.png" />
    </Frame>

    The first time you open Settings, a short guide highlights the tile. You
    can replay it any time from **Settings → Get help → How to use the app**.

    <Frame caption="The Developers step of the Settings guide">
      <img src="https://mintcdn.com/laces/TvNmP5tvH8tqleQI/images/app/settings-developers-showcase.png?fit=max&auto=format&n=TvNmP5tvH8tqleQI&q=85&s=59350dc5d7700879b3a82a9a0427758e" alt="The Laces Settings guide highlighting the Developers tile with the text: Create API keys and webhooks so your website can show your events and take registrations" width="1206" height="2622" data-path="images/app/settings-developers-showcase.png" />
    </Frame>
  </Step>

  <Step title="Start a new key">
    In **API keys**, tap **Create key**. Until you have made one, the section
    shows an empty state.

    <Frame caption="Developers before any key exists">
      <img src="https://mintcdn.com/laces/TvNmP5tvH8tqleQI/images/app/developers-empty.png?fit=max&auto=format&n=TvNmP5tvH8tqleQI&q=85&s=d2895692187600dba0942f55acc75bf3" alt="The Laces Developers screen with the API keys and Webhooks sections, each showing a no-items-yet message" width="1206" height="1700" data-path="images/app/developers-empty.png" />
    </Frame>
  </Step>

  <Step title="Name it">
    **Name** is only for you, to tell keys apart, for example "Partner website"
    or "Club site". Use one key per site, so you can revoke one without
    breaking the others.

    <Frame caption="The Create API key form: name, permissions and the Read orders warning">
      <img src="https://mintcdn.com/laces/TvNmP5tvH8tqleQI/images/app/create-key-form.png?fit=max&auto=format&n=TvNmP5tvH8tqleQI&q=85&s=0f9babb0289a2a7c21ff4f20dbe1a893" alt="The Create API key form with the name Website, three permissions switched on and Read orders switched off, with a warning that Read orders exposes names, emails and phone numbers" width="1206" height="2622" data-path="images/app/create-key-form.png" />
    </Frame>
  </Step>

  <Step title="Choose permissions">
    **Permissions** are the [scopes](/concepts/scopes). Three are on by
    default: **Read events**, **Create orders** and **Start hosted checkout**.

    **Read orders** (`orders:read`) is **off** by default. It lets the key read
    every order in your organization *including names, emails and phone
    numbers*. Leave it off unless your server genuinely needs it; webhooks and
    checkout session reads tell you payment outcomes without it.
  </Step>

  <Step title="Enter your domains">
    Two lists, explained [below](#allowed-domains-vs-return-url-domains):

    * **Allowed domains**: websites that may use the publishable key, for
      example `https://shop.example.com`.
    * **Return URL domains**: websites a checkout may send people back to
      afterwards.

    <Frame caption="Domains are added one at a time and shown as chips">
      <img src="https://mintcdn.com/laces/TvNmP5tvH8tqleQI/images/app/create-key-domains.png?fit=max&auto=format&n=TvNmP5tvH8tqleQI&q=85&s=d5c069c8b3cb31c2025e28ceeafe5aab" alt="The Allowed domains and Return URL domains fields, each with one domain added, and the Create key button" width="1206" height="2622" data-path="images/app/create-key-domains.png" />
    </Frame>
  </Step>

  <Step title="Create it and copy the secret">
    Tap **Create key**. The **Your new key** sheet shows the Client ID, the
    **Secret** and the Publishable key, each with a copy button. **Copy all**
    copies all three as a block you can paste into a `.env` file:

    ```bash .env theme={null}
    LACES_CLIENT_ID=LCSCLI-EXAMPLECLNT2345EXAMP
    LACES_CLIENT_SECRET=LCSSECK-EXAMPLE…SECRE-X
    LACES_PUBLISHABLE_KEY=LCSPUBK-ABCDEFGHJKLMNPQRSTUVWXYZ23456789-X
    ```

    **Copy the secret now** and put it in your server's secret manager or
    environment. Laces stores only a hash of it, so it can never be shown
    again. If you lose it, [regenerate your keys](#regenerate-keys).

    <Frame caption="The secret is shown once (secret value blurred here)">
      <img src="https://mintcdn.com/laces/TvNmP5tvH8tqleQI/images/app/key-reveal-once.png?fit=max&auto=format&n=TvNmP5tvH8tqleQI&q=85&s=1e77696cef753fa03d24381efb79dc12" alt="The Your new key sheet showing the client ID, a blurred secret and the publishable key, with Copy all and I've saved the secret buttons" width="1206" height="2622" data-path="images/app/key-reveal-once.png" />
    </Frame>

    After you confirm, the key appears in your list with its secret masked
    to the last four characters.

    <Frame caption="Your keys list">
      <img src="https://mintcdn.com/laces/TvNmP5tvH8tqleQI/images/app/keys-list.png?fit=max&auto=format&n=TvNmP5tvH8tqleQI&q=85&s=1e0f33527abd620233df73f68918d317" alt="The API keys list showing one key named Website with Test and Active badges, client ID, publishable key and a masked secret" width="1206" height="1650" data-path="images/app/keys-list.png" />
    </Frame>

    Tap a key to open its detail screen, where you can edit permissions and
    domains, regenerate the keys or revoke the key.

    <Frame caption="Key detail with Regenerate keys and Revoke key">
      <img src="https://mintcdn.com/laces/TvNmP5tvH8tqleQI/images/app/key-detail.png?fit=max&auto=format&n=TvNmP5tvH8tqleQI&q=85&s=d71c5aee5f253ac1b73b4a6c141a6608" alt="The API key detail screen scrolled to Key actions, showing Save changes, Regenerate keys and Revoke key" width="1206" height="2622" data-path="images/app/key-detail.png" />
    </Frame>
  </Step>
</Steps>

<Warning>
  **The client secret (`LCSSECK-…-X`) is server-only.** It mints tokens that
  can create and pay orders. Keep it in your server's environment or secret
  manager, never in a browser, a mobile app or source control. The token
  endpoint refuses any request that carries an `Origin` header, so it cannot
  be called from a web page even by mistake.
</Warning>

An organization can have up to **10 active API keys**.

## Allowed domains vs return URL domains

| List | Used for | Example |
| - | - | - |
| **Allowed domains** | Browser calls made with the publishable key. The browser's `Origin` must match one entry exactly. Also the page that hosts an [embedded checkout](/guides/embedded-checkout). | `https://shop.example.com` |
| **Return URL domains** | Where [hosted checkout](/guides/hosted-checkout) may send the buyer afterwards (`on_success_url`, `on_cancel_url`). | `https://shop.example.com` |

Rules for both:

* **Exact origins only**: scheme, host and an optional port. No wildcards, no
  paths, no query strings.
  * `https://shop.example.com` is valid.
  * `https://*.example.com` and `https://example.com/events` are not.
* **`https` only**, with one exception for local development (below).
* `https://example.com` and `https://www.example.com` are **different**
  origins. List both if you serve both.
* Up to **20** entries per list.

If a list is empty, publishable-key calls (or hosted checkout sessions) are
refused until you add your domains.

<Note>
  A domain you **add** can take up to **5 minutes** before browsers' CORS
  preflight requests from it succeed. Removing a domain, or removing a
  permission, takes effect on the next request.
</Note>

### Local development domains

You can add `http://localhost:<port>` or `http://127.0.0.1:<port>` to either
list, for example `http://localhost:3000`. The **port is required**, and no
other `http` address is accepted. These are exact entries like any other:
`http://localhost:3000` does not allow `http://localhost:5173`.

Local domains work end to end with your live keys: your local page can call
the API with the publishable key, [hosted checkout](/guides/hosted-checkout)
returns the buyer to an `http://localhost:<port>` return URL listed in
**Return URL domains**, and the [embedded checkout](/guides/embedded-checkout)
modal opens and completes inside a page on a listed `http://localhost:<port>`
origin.

The app marks a key that lists one with **"Local development domain: remove
before launch"**. Remove them once your site is live.

Webhook endpoint URLs are different: they are always public `https`
addresses. See [Webhooks](/guides/webhooks#test-your-endpoint).

## Manage a key

Open the key from **Developers → API keys** to see its values, when it was
last used, and its **Key actions**. You can edit the name, permissions and
domains at any time. A permission you **remove** stops working on the next
request, even for tokens already issued. A permission you **add** applies to
tokens issued after the change, so fetch a new token to use it.

### Regenerate keys

**Regenerate keys** replaces both the secret and the publishable key in one
step. Use it if a secret leaks, if you lose it, or as routine rotation.

1. Tap **Regenerate keys** and read the confirmation. Tap **Regenerate**.

   <Frame caption="The Regenerate keys confirmation">
     <img src="https://mintcdn.com/laces/TvNmP5tvH8tqleQI/images/app/regenerate-confirm.png?fit=max&auto=format&n=TvNmP5tvH8tqleQI&q=85&s=a282e795c9046dba69840a9cf9fb4402" alt="The Regenerate keys confirmation dialog explaining the old secret works for 24 hours and the old publishable key stops immediately, with Cancel and Regenerate buttons" width="1206" height="2622" data-path="images/app/regenerate-confirm.png" />
   </Frame>

2. The app shows the **new secret and the new publishable key once**, with
   copy buttons and **Copy all**. Copy them now.

3. Deploy the new secret to your servers. **The old secret keeps working for
   24 hours**, so there is no downtime on the server side.

4. Update your website with the new publishable key **right away**. **The old
   publishable key stops working immediately.**

Access tokens your server already holds are not cut off: a token issued with
the old secret keeps working until it expires, at most **1 hour** later. To
shut out a leaked secret completely, [revoke](#revoke) the key instead.

The change takes effect immediately. At most two secrets are valid at once,
so regenerating again within 24 hours ends the oldest secret immediately.
Finish one change before starting the next.

### Revoke

**Revoke key** permanently disables the key: its tokens stop working, its
publishable key stops working, and it cannot be reactivated. Create a new key
instead. Revocation takes effect immediately, including for access tokens
already issued.

## Key formats

Every credential is **uppercase and case-sensitive**. Matching is exact, so a
lowercased or edited key is rejected, never corrected. The random part uses
`A`–`Z` and `2`–`9`, without `0`, `O`, `1` and `I`, so keys are unambiguous when
read aloud or typed by hand.

| Credential | Format | Where it lives |
| - | - | - |
| Publishable key | `LCSPUBK-<32>-X` | Browser or server. Public by design. |
| Client secret | `LCSSECK-<52>-X` | **Server only** |
| Client id | `LCSCLI-<20>` | Server. Not secret, but pairs with the secret. |
| Access token | `LCSTOK-<52>-X` | Server. Valid 1 hour. |
| Checkout session id | `LCSCS-<21>` | Appears in URLs |
| Webhook endpoint id | `LCSWHE-<20>` | Identifier only |
| Webhook signing secret | `LCSWHSEC-<52>-X` | **Server only** |

`<32>` means 32 characters from that alphabet. The trailing `-X` is a fixed
literal, not a placeholder.

A quick sanity check before you store a key:

```js theme={null}
const PUBLISHABLE = /^LCSPUBK-[A-HJ-NP-Z2-9]{32}-X$/;
const SECRET      = /^LCSSECK-[A-HJ-NP-Z2-9]{52}-X$/;
const CLIENT_ID   = /^LCSCLI-[A-HJ-NP-Z2-9]{20}$/;
```

<Note>
  Partner credentials work only on `https://connect.laces.run`. Sent to any
  other Laces host they are refused with `401 partner_credential_not_accepted`.
</Note>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.