Skip to main content
The Partner API accepts two credentials, on different routes: Both are sent to the Partner API host, https://connect.laces.run/v1. A Laces app sign-in (a Firebase ID token) is not a Partner API credential and is rejected there with 401 invalid_token. The reverse holds too: a partner credential sent to any other Laces host (for example api.laces.run) is refused with 401 partner_credential_not_accepted.
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.

Server side: OAuth 2.0 client credentials

POST /oauth/token implements the OAuth 2.0 client credentials grant (RFC 6749 §4.4). You send your client id and secret, and get back a bearer token valid for one hour.
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.

The request

Authenticate either with HTTP Basic or with fields in the body. Use exactly one; sending both is 400 invalid_request.
  • Basic: Authorization: Basic base64(client_id + ":" + client_secret). (Strictly, each part is URL-encoded first, but Laces ids and secrets contain only letters, digits, - and _, so encoding changes nothing.)
  • Body: client_id and client_secret fields.
grant_type=client_credentials is always required in the body. The body can be application/x-www-form-urlencoded (the RFC’s format) or JSON. scope is optional: a space-separated list to ask for fewer scopes than the client holds. Leave it out to get all of them.

The response

200, in the bare OAuth shape (no Laces envelope), with Cache-Control: no-store:
Send it on every other call:

Caching the token

Tokens are opaque and valid for 3600 seconds. Cache the token and fetch a new one shortly before it expires, or when a call returns 401 invalid_token. Do not mint a token per API call: the token endpoint allows only about 10 requests a minute for each client id from one IP address (rate limits).
If you run several server instances, share the cached token (for example in Redis) rather than each instance minting its own.

Server-to-server only

Any request to the token endpoint that carries an Origin header is refused with 400 invalid_request, and the endpoint sends no CORS headers. Browsers always add Origin to cross-origin POSTs, so the secret can’t be used from a web page even by mistake.If your framework proxies requests and forwards the browser’s headers, strip Origin before calling Laces.

Token endpoint errors

Errors use the OAuth shape (RFC 6749 §5.2), not the Laces envelope:

Browser side: the publishable key

For code that runs in a visitor’s browser (hosted and embedded checkout), send the publishable key in a header:
The publishable key is public: it’s meant to sit in your page source. That’s safe because the server enforces three things:
  1. Origin must match an allowed domain. The request’s Origin header must exactly equal one of the key’s allowed domains. A request with no Origin (curl, a server) is 403 origin_not_allowed: a publishable key works only from a browser on your site. This guards against a copied key being used on someone else’s site.
  2. It is capped to two scopes, whatever the client holds: events:read and checkout_sessions:write. It can list and read events and create a checkout session. Nothing else, so it can never create, pay or read orders.
  3. CORS is answered only for origins on an allowlist. Content-Type, Accept, Authorization, X-Laces-Publishable-Key and Idempotency-Key are the permitted request headers, and your script can read the Retry-After and X-Request-Id response headers. Cookies are not used.
If a request carries both a bearer token and a publishable key, only the bearer token is considered.

Authentication errors on API routes

Every route other than the token endpoint answers with the Laces envelope and a stable error_code. A 401 also carries WWW-Authenticate: Bearer realm="laces-partner".
On invalid_token, fetch a new token once and retry. If it fails again, check that the key hasn’t been revoked and that you are calling https://connect.laces.run/v1. Changes to a key apply immediately: a revoke cuts off the key and its tokens on the next request, and a removed permission stops working on the next request. After Regenerate keys, tokens already issued keep working until they expire (at most 1 hour); revoke the key to cut them off at once. A newly added allowed domain can take up to 5 minutes to pass browsers’ CORS preflight.