API reference
Base URL:
text
https://api.perkamo.comAuthenticate 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"
}
}| Field | Required | Limit | Notes |
|---|---|---|---|
user_id | yes | 256 chars | Stable user identifier. |
traits | no | JSON object | Non-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.
messageIdbecomes the transaction id (segment_<messageId>), so Segment
retries stay idempotent and never double-award points.
propertiesare stored as event context; the original display name is kept
in context.segment_event.
identifycalls maptraitsonto 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
| Status | Meaning | Action |
|---|---|---|
400 | Invalid body or business rule | Fix payload or show a user-safe message. |
401 | Missing or invalid API key | Check backend secret configuration. |
403 | API key lacks the required scope | Check key scopes. |
404 | Resource not found | Check customer id or endpoint path. |
429 | Rate or usage limit exceeded | Back off and retry later. |
5xx | Temporary server failure | Retry with the same transaction id. |