Why us
Developers
Pricing
iGaming
Theme
Try it free
Developer docs, the full API reference endpoint by endpoint Open docs
For developers

One POST to send activity in. JSON endpoints to read it back out.

Your backend sends one authenticated HTTPS request when something happens. The same backend reads missions, streaks, achievements, leaderboards, points and the shop back out. No SDK, no agent, nothing inbound to your network.

Quickstart

Four steps from an empty account to a mission that completes.

Mint a key and declare what you want to track in the dashboard, then send an event and read the result from your code. The last two steps are a single POST each.

Get your Sandbox keys

Every project has two environments, Sandbox and Live. Mint a key inside Sandbox. It is shown once, because we keep only a SHA-256 hash. If you lose it, revoke it and mint another.

Declare an event

Declare what you want to track: a key such as purchase, and any properties to filter or sum on. An undeclared key is refused at ingest, unless discovery is switched on.

Send your first event

One POST, one header. The first event carrying an unknown playerId creates that customer.

Read the result back

Build a mission that uses the event and send it again. Fetch the result with POST /v1/missions. That response is the shape your front end renders.

the full dependency list
  • An outbound HTTPS client. Whatever your stack already uses.
  • One header. Authorization: Bearer gem_sbox_…
  • Your own id for the customer. We never issue one.
  • Nothing inbound. No SDK, no agent.
The same code runs against Live. You change the key, not the code.
Sending data in

Two write endpoints: one for things that happen, one for facts about the person.

POST /v1/events records a batch of things that happened, such as a purchase. PUT /v1/attributes records facts about the person right now, such as their tier, and the last write wins. Both create the customer on first contact.

  • Batch whenever you can. A thousand events cost little more than one call: authenticating the key is most of the cost.
  • Two timestamps. You send occurredAt. We record arrival separately, so a replayed backlog lands in the right day.
  • Properties are validated against what you declared, unless discovery is switched on, in which case an unknown key defines itself.
  • A 200 doesn't mean every item was accepted. Check results for which succeeded.
  • The customer is created on first contact. No registration call.
POST /v1/events
{
  // required: at least one, at most 1,000 per call
  "events": [
    {
      // required: your own id for the end user, never ours
      "playerId":    "cust_8f2a41c",
      // required: an event key you declared, snake_case
      "event":       "purchase",
      // required: when it happened, not when you sent it
      "occurredAt":  "2026-07-28T18:24:05Z",
      // optional: makes a retry replay instead of duplicate
      "idempotencyKey": "ord_9f31c2",
      // optional: typed, and filterable or summable inside conditions
      "properties": {
        "amount":          64.90,
        "method":          "card",
        "channel":         "mobile-app"
      },
      // optional: IANA zone, used for day boundaries
      "playerTimeZoneId": "Europe/Belgrade"
    }
  ]
}
Field Type Required Notes
events array yes At least one, up to 1,000 per call. A larger batch is refused whole.
playerId string yes Your own id for the customer, up to 200 characters. An unknown value creates it.
event string yes An event you declared, snake_case, active in this environment.
occurredAt timestamp yes When it happened, not when you sent it. An absolute instant: include an offset or a trailing Z.
idempotencyKey string no Unique per event. A retry replays the original rather than duplicating it.
properties object no Checked against the event's definitions: String, Number, Boolean or DateTime.
playerTimeZoneId string no An IANA zone such as Europe/Belgrade. It sets day boundaries.
Reading data back

The endpoints behind a customer's progress screen.

Every POST changes something, and the GETs only read. A progress screen gets everything it needs on the same key you write with: the mission feed with progress per objective, the current and best streak, every achievement held or not, their rank, the points balance and ledger, and the shop.

Endpoint What comes back
POST /v1/missions The mission feed: objectives, rewards and status.
POST /v1/mission-collections/{collectionKey} One collection, in the order you curated it.
POST /v1/missions/{missionKey}/accept Opts a customer into a mission that needs it.
GET /v1/streaks The live run, the longest run, and when it ends.
GET /v1/achievements Every achievement, held or not, each with its own progress.
GET /v1/achievements/{achievementKey} One achievement. Unknown, switched off and hidden all read as 404.
GET /v1/leaderboards/{key} Top, Around or Named entries, with rank and score.
GET /v1/points The current balance. An unknown customer has zero, not a 404.
GET /v1/points/transactions The ledger, newest first, with the cause of each move.
POST /v1/points/adjustments A manual credit or debit, with a reason recorded.
GET /v1/marketplace-items What this customer can see and buy right now.
GET /v1/marketplace-collections/{collectionKey} One curated shop section.
POST /v1/marketplace-items/{key}/purchases Buys one unit, spends the points and records a grant.

A purchase passes six checks, in order. It stops at the first failure, so the error code tells you which one closed.

1 the item exists here
2 it is active and on sale
3 the customer is eligible
4 stock is left
5 under their own limit
6 the balance covers the price
  • Ingest, adjustments and purchases take an idempotency key. A retry returns the original. Nothing is counted twice.
  • One error shape. RFC 9457 problem+json, with a code you switch on, every problem listed at once, and a traceId.
  • You issue what the customer receives. We record the grant against your catalog item. Money never touches us.
Keys, webhooks and limits

One key belongs to one environment for its whole life.

The key you send decides whether you reach Sandbox or Live, and nothing else does. A test key never reaches a real customer.

Diagram: each API key is bound to one environment for its whole life, so a sandbox key reaches only the Sandbox environment and a live key only the Live environment. Staging backend your test traffic Production backend your real customers gem_sbox_9f2c… 256 bits of entropy · SHA-256 at rest gem_live_4b71… shown once · revocable · never re-pointed Sandbox environment own customers, events, missions, points and marketplace Live environment the same objects, entirely separate data A sandbox key cannot reach Live data, even if it leaks.
keys, people and webhooks
  • Prefixed by environment. gem_live_… or gem_sbox_…, so a key identifies itself.
  • Shown once, revocable at any moment. We keep a SHA-256 hash.
  • People sign in separately. Servers hold a project key. Your colleagues use the dashboard. A leaked project key cannot edit a mission.
  • Webhooks are opt-in. Register one or more HTTPS endpoints; we POST catalog grants, mission completions, streak milestones and achievements to every endpoint that wants them, signed with HMAC-SHA256. Delivery repeats, so de-duplicate on deliveryId.
We never ask for database credentials, network access, customer names or payment details.
No batch ingest

One event per request. Tell us early if your volume is high.

not today
No UI components

You get JSON, not a progress bar. Building the screen is real front-end work.

by design
No published client SDKs

Each API serves an OpenAPI document. Generating a typed client is your build step.

not today
Analytics are in the dashboard

Mission funnel, points in and out, active customers, streak outcomes and marketplace sales are all real, requiring sign-in. A project key reads one customer at a time.

dashboard only

Send one event and watch what it moves.

Bring the name of one thing you track and one campaign you want to launch. We will wire it up in a test account and complete it live on the call.