Authentication
The Lifepeaks v2 API uses Bearer API keys for authentication. A key carries the prefix lp_live_ or lp_test_; see Live keys and test keys.
Making authenticated requests
Pass the key in the Authorization header on every request:
Authorization: Bearer lp_live_<your-key>
A key is its prefix, a 32-character hexadecimal key id, an underscore, and a 64-character hexadecimal secret. Example with curl:
curl -H "Authorization: Bearer $LIFEPEAKS_API_KEY" \
https://api.lifepeaks.dk/v2/health
Checking what a key can do
GET /v2/me returns the company the key acts on, the scopes it actually carries, when it
expires, and which settlement modes the company holds. It needs no scope, so any key can
call it. Use it at start-up rather than discovering a missing scope as a 403 in
production.
curl -H "Authorization: Bearer $LIFEPEAKS_API_KEY" \
https://api.lifepeaks.dk/v2/me
{
"api_version": "v2",
"company": { "id": "acme-hotels", "name": "Acme Hotels" },
"credential": {
"id": "4f2c9a71b83d4e6fa0c15d27e93b6a08",
"label": "Storefront production",
"environment": "live",
"scopes": ["catalog:read", "quotes:create", "orders:create", "orders:read", "checkout:create"],
"expires_at": "2027-02-01T00:00:00+00:00"
},
"commerce": {
"modes": ["lifepeaks_checkout"],
"default_mode": "lifepeaks_checkout",
"negotiated_settlement": false
}
}
credential.id is the public key id — the middle segment of the key, safe to log. The
secret half never appears in any response.
Live keys and test keys
A key is either lp_live_… or lp_test_…, fixed when it is created. GET /v2/me reports which one you hold, as environment.
Both read and write the same data. There is one Lifepeaks database per environment, so a gift card issued with a test key is a real row you can look up, redeem and see in the back office. A test key is not a separate world to develop in.
The difference is where it may take money. What makes a payment real is the environment, not the key: the demo environment's payment credentials belong to QuickPay's test account, so every payment made there is a test payment — use QuickPay's test cards. Production's credentials are live.
A test key cannot take money in production. Starting a checkout session or a refund there with an lp_test_ key is refused, before anything is charged:
{
"error": {
"code": "test_key_not_accepted_in_production",
"message": "A test key cannot take or return a payment on the production environment, whose payment credentials are live. Use a live key here, or this test key on the demo environment."
}
}
That answer is 409. On demo a test key takes payments like any other key.
Mint one with POST /v2/api-keys by sending "environment": "test". Omit the field and you get a live key, which is what every existing caller already gets. The admin screen still issues live keys only.
A test key can only mint test keys. Asking for live from one, or omitting environment so the default would apply, answers 422 with error.code of validation_failed and environment named in error.fields. It is refused rather than quietly downgraded, so a caller is never left thinking it asked for something it did not get. That makes a test key safe to hand to a contractor with credentials:write: they cannot turn it into a credential that charges real cards.
Rate limits
Every /v2 request spends one request from a budget held on the key that made it. The default budget is 600 requests per minute per key, and it refills continuously, so a client that stays under the rate never sees a rejection. Each key has its own budget: exhausting one does not affect another, even in the same company.
Every response carries the state of that budget:
| Header | Meaning |
|---|---|
X-Rate-Limit-Limit | Requests a full window allows |
X-Rate-Limit-Remaining | Requests left right now |
X-Rate-Limit-Reset | Seconds until the budget is full again |
A request that arrives with an empty budget is answered 429 and no work is done:
{
"error": {
"code": "rate_limited",
"message": "Too many requests for this API key. Retry after the interval in the Retry-After header."
}
}
Retry-After gives the seconds to wait. Wait at least that long, then retry: a 429 is always safe to retry, and retrying a write with the same Idempotency-Key cannot duplicate it.
GET /v2/pickup-points has a second, tighter limit of its own — 60 lookups per company per minute — because it reaches the courier on our account. See Collection points.
Scopes
Every API key carries one or more scopes that determine what it can do.
| Scope | What it permits |
|---|---|
companies:read | List the companies the key may act for, and read one of them |
brand:read | Read a company's brand profile and brand assets |
brand:write | Update the brand profile and replace or delete brand assets |
credentials:read | List API keys without plaintext values |
credentials:write | Create and revoke API keys |
orderpage:read | Read order-page config, schema, revisions, drafts, and previews |
orderpage:write | Update config and create, edit, publish, or restore revisions |
pdf_templates:read | Read and preview branded gift-card PDF templates |
pdf_templates:write | Upload, publish, and revert branded gift-card PDF templates |
items:read | Search and inspect items, list events, list gift-card campaigns |
items:write | Redeem, refund, cancel, activate and resend items; redeem a whole order |
catalog:read | List products available to the acting company |
quotes:create | Request authoritative Lifepeaks pricing |
orders:create | Create unpaid headless orders; compatibility issuance additionally needs orders:settle and a contract |
orders:read | Read canonical order, payment, and fulfillment state, and download the fulfilled gift-card PDF |
checkout:create | Create Lifepeaks-hosted QuickPay checkout sessions |
orders:settle | Request negotiated, entitlement-gated settlement |
orders:refund | Return money on an order. Deliberately not implied by orders:settle: a key that may settle an invoice should not thereby move money back out |
webhooks:read | List webhook endpoints and deliveries |
webhooks:write | Register and disable endpoints, rotate secrets, and replay deliveries |
reports:read | Analytics, claimed-item transactions, subscribers, event participants |
Scopes are independent — a key with only orderpage:read cannot create or revoke keys, a key with items:read cannot redeem anything, and pdf_templates:write does not imply pdf_templates:read. Every endpoint states its required scope; see Endpoints and Events & Reporting.
Four scope boundaries are worth noting:
GET /v2/meandGET /v2/healthneed no scope at all. Any valid key can describe itself and check that the API is reachable, which makes/v2/methe right place to discover what a key can actually do.- Event participants need
reports:read, notitems:read— the rows carry participant personal data. - Headless order creation, reading, and checkout use separate scopes. A return-page backend can hold
orders:readwithout permission to create new orders. orders:settleis necessary but not sufficient. Settlement also requires an active negotiated entitlement for the company, credential, and mode.GET /v2/mereports which modes are available.
When issuing a new key you cannot assign scopes that exceed your own.
Issuing API keys
Via the admin UI
Log in to the Lifepeaks admin panel, open your company's settings, and choose the API tab. The Lifepeaks API v2 section there lists your existing keys and issues new ones. It is separate from the Lifepeaks API block above it, which holds the v1 OAuth client id and secret.
To issue a key, give it a label, tick the scopes it needs, choose Live or Test, and optionally set an expiry between 1 and 365 days. The plaintext key is shown once, immediately after creation, and never again.
Who can issue a key:
- A super admin (company owner) can manage keys for their own company.
- A Lifepeaks system administrator can manage keys for any company, from that company's own settings screen.
- No other role has access.
Live and test keys read and write the same data — there is one Lifepeaks database per environment, so a test key is not a sandbox. The difference is money: on production a test key is refused for real payments. Use a test key for integration work and a live key for real traffic.
Via the REST API
POST /v2/api-keys requires credentials:write scope.
POST /v2/api-keys
Authorization: Bearer lp_live_<your-key>
Content-Type: application/json
{
"label": "My integration key",
"scopes": ["items:read", "items:write"],
"expires_in_days": 90
}
| Field | Required | Notes |
|---|---|---|
label | yes | Human-readable name for the key |
scopes | yes | Any subset of your own scopes |
expires_in_days | no | Integer 1–365, counted from creation. Omit to issue a non-expiring key. |
Response 201:
{
"id": 42,
"label": "My integration key",
"scopes": ["items:read", "items:write"],
"key": "lp_live_4f2c9a71b83d4e6fa0c15d27e93b6a08_5d8e1c04b7a9f3620e4d81c7a5b93f0d2e6c48a1b309f75d84e2c6b19a03d7f5",
"expires_at": "2026-11-25T00:00:00+00:00"
}
expires_at is an ISO 8601 timestamp, or null when the key never expires. Note that the listing endpoint reports the same instant as YYYY-MM-DD HH:MM:SS instead.
The plaintext key is returned once in the key field. Lifepeaks stores only a hash of it, so it cannot be retrieved again — write it to your secret store immediately, and if you lose it, revoke the key and issue another.
Key management always acts on the key's own company and takes no company parameter, so an agency cannot mint or revoke credentials inside a company assigned to it.
Listing and revoking keys
List all keys for the authenticated company (credentials:read):
GET /v2/api-keys
The listing includes revoked and expired keys, with expires_at, revoked_at, and last_used_at, which makes it an audit of every credential ever issued. Plaintext values are never included. This route takes no query parameters and no pagination.
Revoke a key by its numeric ID (credentials:write):
DELETE /v2/api-keys/{id}
Returns 204 No Content on success, or 404 if no such key belongs to your company. Revocation takes effect immediately with no grace period, so deploy the replacement first. It cannot be undone.
Error responses
| HTTP status | Error code | Meaning |
|---|---|---|
401 Unauthorized | unauthorized | No key supplied, or the key is invalid / expired / revoked |
403 Forbidden | insufficient_scope | Key is valid but lacks the required scope |
404 Not Found | not_found | The record does not exist for the company the request acts on |
409 Conflict | test_key_not_accepted_in_production | A test key tried to take or return a payment in production |
422 Unprocessable Entity | validation_failed | The request broke a business or input rule |
429 Too Many Requests | rate_limited | The key's request budget is empty. Wait for Retry-After seconds |
A 401 body looks like this:
{
"error": {
"code": "unauthorized",
"message": "Valid credentials required."
}
}
A 403 names the scope that was missing:
{
"error": {
"code": "insufficient_scope",
"message": "Missing scope: orderpage:write"
}
}