POST /customers
Create the customer record, keyed by your external id.
Developers
The shortest path from credentials to a first reward issued.
Overview
Every integration reduces to the same loop: describe a customer, give them a wallet, define what earns value, send the events your business already produces, and read the balance that results. This page walks that loop once, in the sandbox environment, with nothing left out.
The goal is a working end-to-end path in a test environment before any decision about assets, custody or production rollout. Nothing on this page touches production, and nothing here issues anything on a public network.
Each step is the exact call the specification defines — the same paths, headers and error codes that the Examples page uses at greater length.
Requirements
Installation
No package is published yet, so the setup is one helper around fetch. This is the same helper the Examples page builds; it adds the required headers and unwraps the response envelope.
const BASE = 'https://sandbox-api.matinee.ai/v1'
export async function call<T>(
path: string,
options: { idempotencyKey?: string; body?: unknown; method?: string } = {},
): Promise<T> {
const response = await fetch(BASE + path, {
method: options.method ?? (options.body ? 'POST' : 'GET'),
headers: {
Authorization: `Bearer ${process.env.MATINEE_API_KEY}`,
'X-Organization-ID': process.env.MATINEE_ORG_ID!,
'X-Environment': 'sandbox',
'X-Request-ID': crypto.randomUUID(),
'Content-Type': 'application/json',
...(options.idempotencyKey ? { 'Idempotency-Key': options.idempotencyKey } : {}),
},
body: options.body ? JSON.stringify(options.body) : undefined,
})
const payload = await response.json()
if (!response.ok) throw new Error(`${payload.error.code} (${payload.meta.request_id})`)
return payload.data as T
}The headers are §4 of the specification; the { data, meta } envelope is §5. Success and failure both carry meta.request_id — log it, it is what support will ask for.
The path
Create the customer record, keyed by your own external id.
Give the customer somewhere to hold what they earn.
Create the points asset and activate it — nothing can be issued against an inactive asset.
Create a programme and add one rule to its draft version, then publish.
Send one business event, the same shape your systems already produce.
Read the wallet balance and see the reward the rule issued.
Examples
Idempotency keys on every write mean the whole file can be re-run safely: a repeated call returns the original result instead of creating a duplicate.
// 1–2. A customer, and somewhere to hold what they earn.
const customer = await call('/customers', {
idempotencyKey: 'quick-start-customer',
body: { external_id: 'user-0001', email: 'ada@example.com', country: 'GB' },
})
const wallet = await call('/wallets', {
idempotencyKey: `wallet-${customer.id}`,
body: { customer_id: customer.id, type: 'customer' },
})
// 3. The asset being earned. Created configured, then activated.
const points = await call('/assets', {
idempotencyKey: 'quick-start-points',
body: { name: 'Points', symbol: 'PTS', type: 'points', decimals: 0 },
})
await call(`/assets/${points.id}/activate`, { idempotencyKey: `activate-${points.id}` })
// 4. A programme with one rule: 10 points per completed signup. Rules attach
// to a draft version and take effect when the version is published.
const programme = await call('/reward-programs', {
idempotencyKey: 'quick-start-programme',
body: { name: 'Getting started', asset_id: points.id },
})
await call('/reward-rules', {
idempotencyKey: 'quick-start-rule',
body: {
program_id: programme.id,
event_type: 'signup.completed',
actions: [{ type: 'issue', asset_id: points.id, amount: { fixed: '10' } }],
},
})
await call(`/reward-programs/${programme.id}/publish`, {
idempotencyKey: `publish-${programme.id}-v1`,
})
// 5. The event. From here on, this is the only call your system makes.
await call('/events', {
idempotencyKey: 'signup-user-0001',
body: {
type: 'signup.completed',
external_event_id: 'signup-user-0001',
customer_id: customer.id,
occurred_at: new Date().toISOString(),
},
})
// 6. The balance. Rewards are issued asynchronously, so poll or subscribe to
// the reward.issued webhook rather than asserting on the very next read.
const balances = await call(`/wallets/${wallet.id}/balances`)Events are deduplicated on organization, environment and external_event_id — sending the same event id twice returns the original result rather than issuing twice.
Run it
The repository ships a sandbox reference server that implements this exact surface — the headers, the envelope, idempotency, rate limits, the ledger and the reward engine — in memory, on localhost. The hosted sandbox does not exist yet; this does.
$ git clone https://github.com/matinee-ai/matinee.ai && cd matinee.ai
$ npm ci
$ npm run sandbox
# Prints a base URL, an API key and an organization id, ready to paste
# into the helper above. State is in memory and dies with the process.The integration tests run every snippet on this page against this server through the reference SDK — the loop above is executed, not aspirational.
Playground
The reward engine, the double-entry ledger and the store behind the loop above are dependency-free TypeScript — so this page runs them. Publish a rule, complete an order, send the same order twice, simulate, redeem. Every number is derived from real ledger entries living in this browser tab; nothing calls an API.
Nothing posted yet. Complete an order.
This playground runs the sandbox reference server’s actual store, ledger and engine modules, compiled into this page — the same code the integration tests drive over HTTP. Every balance above is derived from real double-entry entries, which is why the ledger always nets to zero. It calls no API, stores nothing, and resets when you leave.
Best practices
API
Eight endpoints, each gated by a permission your key must hold. Every response carries the same { data, meta } envelope.
Create the customer record, keyed by your external id.
Create a wallet for a customer.
Create the asset, then activate it. Activation is a transition with preconditions, not a flag.
Create the programme and add a rule to its draft version.
Publish the draft. The published version becomes immutable.
Submit a business event. The highest-volume write on the whole surface.
Read what the wallet holds, served from a snapshot over the ledger.
Error handling
Every error carries a stable code, a type that maps to the HTTP status, and the request id. On this path, expect these:
The full registry — 84 codes, each mapped to a type and status — is on the API Reference page.
Reference
Access is reviewed. Tell us what you are building and we will let you know when the sandbox opens.