API referenceStable · 7 min read

API reference


Base URL:

text

https://api.perkamo.com

Authenticate server calls with x-perkamo-api-key. All bodies are JSON. The official SDKs sign mutating requests automatically.

If you call mutating endpoints directly over HTTP, also send x-perkamo-timestamp and x-perkamo-signature. Build the signature input from the exact request you are sending:

text

v1.{unix_timestamp}.{HTTP_METHOD}.{path_and_query}.{sha256_hex(raw_body)}

Then sign that text with HMAC-SHA256 using the server API key and send the result as x-perkamo-signature: v1=<hex>.

Create server keys in Server keys. Each server key is scoped to one Space, so event and customer requests do not need a space field in the request body.

POST /v1/identify

Creates or updates a customer.

json

{
"user_id": "customer_123",
"traits": {
"email": "customer@example.test",
"name": "Customer Test",
"crm_id": "crm_123",
"plan": "premium"
}
}
FieldRequiredLimitNotes
user_idyes256 charsStable user identifier.
traitsnoJSON objectNon-secret customer facts.

Use your application's stable user id as user_id. Calling identify again updates traits for the same customer.

POST /v1/events

Ingests one activity event.

After sending events, inspect delivery and rule outcomes in Events and Customers.

json

{
"user_id": "customer_123",
"event": "purchase.completed",
"transaction_id": "order_1092",
"context": {
"amount": 12900,
"currency": "CZK"
},
"occurred_at": "2026-06-01T10:00:00.000Z"
}

json

{
"applied": true,
"duplicate": false,
"delta": [{ "wallet": "points", "amount": 50 }],
"leveled_up": false,
"unlocked": [{ "type": "perk", "key": "welcome_member", "name": "Welcome member" }],
"wallet_state": { "points": 50 },
"level": {
"wallet": "points",
"level": 1,
"current": 50,
"nextLevelAt": 100,
"currentLevelAt": 0,
"progress": 0.5
}
}

POST /v1/events:batch

Ingests up to 100 events.

json

{
"events": [
{
"user_id": "customer_123",
"event": "page.viewed",
"transaction_id": "pageview_01",
"context": { "path": "/pricing" }
}
]
}

Each event still needs its own transaction_id. Batch requests are not a replacement for all-or-nothing domain transactions in your backend.

POST /v1/integrations/segment

Accepts a single Segment track or identify call and maps it onto the trusted event pipeline. Authenticate with a server key that has the events:write scope, the same as POST /v1/events. Status: beta.

json

{
"type": "track",
"userId": "customer_123",
"event": "Order Completed",
"messageId": "segment-message-id",
"properties": { "revenue": 42.5, "currency": "EUR" },
"timestamp": "2026-07-01T10:00:00.000Z"
}

Mapping rules:

  • The Segment event name becomes a stable dotted event key: Order Completed

maps to order.completed. Write your earning rules against the mapped key.

  • messageId becomes the transaction id (segment_<messageId>), so Segment

retries stay idempotent and never double-award points.

  • properties are stored as event context; the original display name is kept

in context.segment_event.

  • identify calls map traits onto the customer profile, the same as

POST /v1/identify.

Forward only server-side sources. Browser-only analytics events should not become authoritative points or rewards.

POST /v1/client-tokens

Creates a short-lived client token after your backend has authenticated the current user. This endpoint is called from trusted backend code with a signed server API key request, not from browser code. It requires both events:write and customer:read. Self-signing with a signing key is preferred when you want to avoid a Perkamo round trip.

json

{
"user_id": "customer_123",
"kid": "csk_live_...",
"scope": ["customer:read", "events:write"],
"events": ["page.viewed"],
"ttl_seconds": 600
}

The token's scope and event claims are clamped to the signing key policy during /v1/client/* verification. For stream tokens, request ["stream:read"] on a separate application route and pass that token to the browser SDK's getStreamToken. See Realtime streaming for the full stream setup.

POST /v1/client-tokens:verify

Introspects a client token against the Space signing keys. Use it in development to confirm a token your backend minted is valid, correctly scoped and unexpired before you ship the browser integration. Requires customer:read.

json

{ "token": "eyJ..." }

json

{
"valid": true,
"kid": "csk_live_...",
"subject": "customer_123",
"scope": ["customer:read", "events:write"],
"events": ["page.viewed"]
}

When valid is false, reason explains why (for example malformed, expired or a scope mismatch). The SDK helper verifyClientToken(token) and the console token tester call this endpoint.

POST /v1/client/events

Sends one browser-safe event with a short-lived client token. Authenticate with Authorization: Bearer <client-token> and the events:write client scope. The customer and Space come from the verified token, so this body never accepts user_id or space:

json

{
"event": "article.read",
"transaction_id": "article:introduction:customer_123",
"context": { "article_id": "introduction" },
"occurred_at": "2026-06-01T10:00:00.000Z"
}

The signing key policy can restrict which event names the token may emit. Purchases, subscriptions and other financially important facts should continue to use a server SDK from your trusted backend.

POST /v1/client/events:batch

Sends up to 100 client events. Each item has the same shape as POST /v1/client/events and requires its own stable transaction_id. The verified token subject is applied to every item.

GET /v1/client/customer/me

Returns the current customer bound to the client token subject. Requires the customer:read client scope. The response uses the same customer shape as GET /v1/customer/{user_id}.

The Browser SDK's getCustomerJson() helper omits customer traits by default. Request or render traits only when the current application screen needs them.

GET /v1/client/customer/me/stream

Streams the current customer projection as Server-Sent Events. Use a dedicated short-lived token whose only scope is stream:read:

text

GET /v1/client/customer/me/stream?token=<stream-token>

The first message contains the current projection and later messages contain a fresh projection after customer state changes. The connection closes when the token expires. Do not put a regular customer:read or events:write token in the URL; use the Browser SDK's separate getStreamToken provider. See Realtime streaming for setup examples.

GET /v1/customer/{user_id}

Reads current customer state.

Response excerpt:

json

{
"space_id": "7c2fc0dd-86af-44f4-a1b0-99b602f99c0f",
"user_id": "customer_123",
"traits": {},
"wallets": { "points": 50 },
"level": {
"wallet": "points",
"level": 1,
"current": 50,
"nextLevelAt": 100,
"currentLevelAt": 0,
"progress": 0.5
},
"perks": [
{
"key": "welcome_member",
"unlocked_at": "2026-06-01T10:00:00.000Z",
"metadata": {
"trigger": { "type": "points_threshold", "wallet": "points", "amount": 30 }
}
}
],
"next_perks": [
{
"key": "active_user",
"name": "Active user",
"description": "Daily habit perk for apps like progress trackers or banking."
}
],
"achievements": {
"completed": [],
"in_progress": [],
"unlocked": []
},
"flags": {
"levels": { "level_1": true },
"has_level_at_least": { "level_1": true },
"perks": { "welcome_member": true }
},
"events": [],
"streaks": [],
"active_boosts": []
}

GET /v1/customers

Lists up to 50 recent customers for the authenticated Space. Use q to search by customer id, e-mail or name traits.

bash

curl "https://api.perkamo.com/v1/customers?q=alice" \
-H "x-perkamo-api-key: $PERKAMO_SECRET_KEY"

json

{
"customers": [
{
"user_id": "customer_123",
"display": {
"primary": "alice@example.com",
"secondary": "Alice Example"
},
"traits": { "email": "alice@example.com", "name": "Alice Example" },
"wallets": { "points": 1920 },
"achievements": 1,
"events": 4
}
]
}

POST /v1/customer/{user_id}/adjust

Adjusts a customer wallet balance and optionally unlocks a perk. This endpoint is for trusted operator or backend support workflows, not customer-facing activity tracking.

Requires the customer:adjust scope and a signed request.

json

{
"wallet": "points",
"amount": 500,
"perk_key": "welcome_member"
}

Perkamo records the operation as an adjustment event and writes customer wallet ledger entries. Balances never go below zero.

POST /v1/rewards/redeem

Redeems a configured reward by subtracting wallet cost and returning a declarative effect.

json

{
"user_id": "customer_123",
"reward_key": "free_shipping",
"transaction_id": "redeem_free_shipping_order_1092"
}

Perkamo returns the reward effect. Your system remains responsible for final fulfillment unless a dedicated fulfillment integration is active.

The effect object is discriminated by type: coupon, free_shipping, gift_card, entitlement or custom. Use entitlement to reward customers with a plan upgrade — a discount on a paid plan, or the plan free for a limited time:

json

{
"type": "entitlement",
"grant": "plan_discount",
"plan": "premium",
"discountPercent": 25,
"durationMonths": 3,
"provider": "external"
}

Apply the grant in your backend (set the plan and its expiry, or create a billing coupon) from a verified redemption, keyed by transaction_id so a replay never re-grants.

GET /v1/program

Returns the authenticated Space blueprint: earning rules, rewards, perks, achievements, levels, integration status, metrics and usage.

When an economy module is configured, the response also carries an economySummary with your targetRoi and the current actualRoi (value generated versus reward cost), so your own dashboards can surface the same ROI readout the console shows.

Edit the visible rule and wallet configuration from Program rules and Program wallets.

Earning rule rewards use either a fixed integer amount or a safe arithmetic formula derived from event context:

json

{
"earningRules": [
{
"event": "purchase.completed",
"title": "Purchase completed",
"rewards": [{ "wallet": "points", "formula": "{{product_price}} * 10" }]
},
{
"event": "lesson.completed",
"title": "Lesson completed",
"rewards": [{ "wallet": "points", "amount": 100 }]
}
]
}

Formula variables reference numeric event context values with {{path}}. Perkamo supports +, -, *, / and parentheses, then rounds the result down before applying wallet boosts.

GET /v1/usage

Returns current billing-period usage meters for the authenticated Space.

POST /v1/admin/config/{module}

Uploads a versioned configuration module. Requires the admin:config scope.

Allowed modules in v1 are events, leveling, wallets, perks, achievements, boosts and economy. The economy module holds the ROI targets and per-action values described in Core concepts → ROI economy; Perkamo back-solves point mechanics from it.

json

{
"version": "2026-06-01",
"active": true,
"body": {}
}

Status guide

StatusMeaningAction
400Invalid body or business ruleFix payload or show a user-safe message.
401Missing or invalid API keyCheck backend secret configuration.
403API key lacks the required scopeCheck key scopes.
404Resource not foundCheck customer id or endpoint path.
429Rate or usage limit exceededBack off and retry later.
5xxTemporary server failureRetry with the same transaction id.