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:

http
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:

bash
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.

bash
curl -H "Authorization: Bearer $LIFEPEAKS_API_KEY" \
  https://api.lifepeaks.dk/v2/me
json
{
  "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:

json
{
  "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:

HeaderMeaning
X-Rate-Limit-LimitRequests a full window allows
X-Rate-Limit-RemainingRequests left right now
X-Rate-Limit-ResetSeconds until the budget is full again

A request that arrives with an empty budget is answered 429 and no work is done:

json
{
  "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.

ScopeWhat it permits
companies:readList the companies the key may act for, and read one of them
brand:readRead a company's brand profile and brand assets
brand:writeUpdate the brand profile and replace or delete brand assets
credentials:readList API keys without plaintext values
credentials:writeCreate and revoke API keys
orderpage:readRead order-page config, schema, revisions, drafts, and previews
orderpage:writeUpdate config and create, edit, publish, or restore revisions
pdf_templates:readRead and preview branded gift-card PDF templates
pdf_templates:writeUpload, publish, and revert branded gift-card PDF templates
items:readSearch and inspect items, list events, list gift-card campaigns
items:writeRedeem, refund, cancel, activate and resend items; redeem a whole order
catalog:readList products available to the acting company
quotes:createRequest authoritative Lifepeaks pricing
orders:createCreate unpaid headless orders; compatibility issuance additionally needs orders:settle and a contract
orders:readRead canonical order, payment, and fulfillment state, and download the fulfilled gift-card PDF
checkout:createCreate Lifepeaks-hosted QuickPay checkout sessions
orders:settleRequest negotiated, entitlement-gated settlement
orders:refundReturn 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:readList webhook endpoints and deliveries
webhooks:writeRegister and disable endpoints, rotate secrets, and replay deliveries
reports:readAnalytics, 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/me and GET /v2/health need no scope at all. Any valid key can describe itself and check that the API is reachable, which makes /v2/me the right place to discover what a key can actually do.
  • Event participants need reports:read, not items:read — the rows carry participant personal data.
  • Headless order creation, reading, and checkout use separate scopes. A return-page backend can hold orders:read without permission to create new orders.
  • orders:settle is necessary but not sufficient. Settlement also requires an active negotiated entitlement for the company, credential, and mode. GET /v2/me reports 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.

http
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
}
FieldRequiredNotes
labelyesHuman-readable name for the key
scopesyesAny subset of your own scopes
expires_in_daysnoInteger 1365, counted from creation. Omit to issue a non-expiring key.

Response 201:

json
{
  "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):

http
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):

http
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 statusError codeMeaning
401 UnauthorizedunauthorizedNo key supplied, or the key is invalid / expired / revoked
403 Forbiddeninsufficient_scopeKey is valid but lacks the required scope
404 Not Foundnot_foundThe record does not exist for the company the request acts on
409 Conflicttest_key_not_accepted_in_productionA test key tried to take or return a payment in production
422 Unprocessable Entityvalidation_failedThe request broke a business or input rule
429 Too Many Requestsrate_limitedThe key's request budget is empty. Wait for Retry-After seconds

A 401 body looks like this:

json
{
  "error": {
    "code": "unauthorized",
    "message": "Valid credentials required."
  }
}

A 403 names the scope that was missing:

json
{
  "error": {
    "code": "insufficient_scope",
    "message": "Missing scope: orderpage:write"
  }
}