API Reference

API Reference

The PlaylogiQ public REST API is versioned under /api/v1.

  • Base URL (reference deployment): https://crm-api.logiqdesk.com/api/v1
  • Machine-readable spec (OpenAPI 3.1): /api/v1/openapi.json

The API is self-hosted, so your installation’s host may differ — substitute your own domain. The OpenAPI document is the source of truth; paste it into any OpenAPI viewer (Swagger UI, Redoc, Insomnia, Postman) to explore request/response schemas interactively.

Authentication

Every endpoint except GET /health and the spec itself requires an operator API key in the x-api-key header:

x-api-key: plq_your_api_key

Keys can be brand-pinned (recommended) or tenant-wide (legacy), and carry least-privilege scopes. See PAM Integration → Authentication for the full model. In short:

  • A brand-pinned key writes only to its brand; any x-brand hint is ignored.
  • A tenant-wide key selects the brand per request with x-brand (header) or ?brand= (query) — a brand id or slug; the tenant’s default brand is used when omitted.
x-api-key: plq_your_api_key
x-brand: main          # tenant-wide keys only — brand id or slug

Conventions

  • Content type: application/json for all request and response bodies.
  • Single resources return { "data": { … } }.
  • Collections return one of:
    • { "data": [ … ], "pagination": { page, limit, total, pages } } — Players, Missions.
    • { "campaigns": [ … ], "total": n } / { "templates": [ … ], "total": n } — with limit/offset query params.
    • { "data": [ … ] } — most gamification/segment/bonus/tournament lists.
    • { "data": [ … ], "next_cursor": string|null } — cursor/keyset exports.
  • Errors return { "error": string, "code"?: string, "details"?: object } with an HTTP status. code is one of UNAUTHORIZED, BAD_REQUEST, NOT_FOUND, FORBIDDEN, INTERNAL_ERROR.

Endpoint index

ResourceEndpoints
SystemGET /health
EventsPOST /events
PlayersGET /players · POST /players · GET /players/{id} · PATCH /players/{id} · GET /players/{id}/next-best-action
SegmentsGET /segments · POST /segments · GET /segments/{id}/export
CampaignsGET /campaigns · POST /campaigns · GET /campaigns/{id} · PATCH /campaigns/{id} · DELETE /campaigns/{id}
NotificationsGET/POST /notifications/templates · GET/PATCH/DELETE /notifications/templates/{id} · POST /notifications/send
GamificationGET/POST + GET/PATCH/DELETE {id} for levels, missions, badges, currencies · GET /gamification/player/{playerId}
BonusesGET/POST /bonuses · GET/PATCH/DELETE /bonuses/{id}
TournamentsGET/POST /tournaments · GET/PATCH/DELETE /tournaments/{id}
AnalyticsGET /analytics/summary · /timeseries · /revenue · /cohorts · GET /reports/run
ExportGET /export/{dataset} · GET /segments/{id}/export

System

GET /health

Public (no API key). Returns { status: "ok"|"degraded", version, service, checks, timestamp }. 503 when degraded.


Events

POST /events

Ingest a single event or a batch (up to 100). Requires the events:write scope. Players are upserted by player_id. See Event ingestion API for the full contract (idempotency, mapping, brand pinning).

Single:

{ "player_id": "player-123", "event_type": "deposit",
  "event_id": "evt-9a7f", "payload": { "amount": 100, "currency": "EUR" },
  "timestamp": "2026-07-09T10:15:30Z", "session_id": "sess-42" }

Batch: { "events": [ … ] } (1–100).

Response 200: { received, stored, deduped, rejected?, ignored?, unknown_event_types?, rejected_event_types?, status: "queued", timestamp }.


Players

GET /players

Query: page (1), limit (20, max 100), search (email or external_id), status (active/suspended/self_excluded/closed). Brand-scoped. → { data: Player[], pagination }.

POST /players

Idempotent on (brand, external_id); syncs language/currency onto the profile. Body: external_id (required); email, phone, status, metadata, language (en/it/es), currency (3-letter ISO), brand (id or slug). → 201 { data: Player }.

GET /players/{id}

id is the PlaylogiQ player UUID (not external_id). Returns the player plus its joined profile. → { data: Player }.

PATCH /players/{id}

Body: status, email, phone, metadata (shallow-merged). → { data: Player }.

GET /players/{id}/next-best-action

Recommendation derived from churn/value/engagement scores + lifecycle stage. → { data: { player_id, action, priority: high|medium|low, reason, channelHint: personal|email|push|any } }.

Player: id, external_id, email, phone, status, metadata, created_at, updated_at.


Segments

GET /segments

Query: type. Brand-scoped. → { data: Segment[] }.

POST /segments

Body: name, type (required); rules (object), brand. → 201 { data: Segment }.

GET /segments/{id}/export

Cursor-paginated keyset export of members (columns: external_id, email, phone, name, consent, country). Query: cursor, limit (1–1000, default 200), format (json/csv). CSV returns the cursor in the x-next-cursor header. → { segment, data[], next_cursor }.

Segment: id, name, type, rules, created_at, updated_at.


Campaigns

GET /campaigns

Query: status (draft/scheduled/running/paused/completed/cancelled), limit (20, max 100), offset. Brand-scoped. → { campaigns: Campaign[], total }.

POST /campaigns

Body: name, type (one_shot/recurring/triggered/journey), channels (≥1 of email/telegram/push/in_app); description, target_segment_id, brand. → 201 { data: Campaign }.

The public API exposes four campaign channels (email, telegram, push, in_app). The dashboard additionally offers SMS and WhatsApp editors; those two are managed in the UI, not via the POST /campaigns API.

GET /campaigns/{id}{ data: Campaign }.

PATCH /campaigns/{id}

Update fields or transition status (validated: draft→scheduled|running, scheduled→running|cancelled, running→paused|cancelled, paused→running|cancelled; completed & cancelled are terminal). Body: name, description, status, channels, target_segment_id, config, schedule.

DELETE /campaigns/{id}

Soft-cancel (sets status: cancelled). → { data: Campaign }.

Campaign: id, name, description, status, type, channels, target_segment_id, schedule, config, stats, created_at, updated_at.


Notifications

GET /notifications/templates

Query: channel (email/telegram/push/in_app), active (default true), limit, offset. Brand-scoped. → { templates: NotificationTemplate[], total }.

POST /notifications/templates

Variables are auto-extracted from {{handlebars}} placeholders in body + subject. Body: name, channel, body; locale (en/it/es), subject, variables, brand. → 201 { data: NotificationTemplate }.

GET/PATCH/DELETE /notifications/templates/{id}

PATCH updates any field (+ active); DELETE soft-deletes (active: false).

POST /notifications/send

Enqueue a single notification to a player (async delivery). Body: player_id, channel; template_id, payload. → 202 { queued, notification_id, data }.

NotificationTemplate: id, name, channel, locale, subject, body, variables, active, created_at, updated_at.


Gamification

Every resource follows the same shape: GET/POST on the collection (brand-scoped) and GET/PATCH/DELETE on {id}. DELETE returns { data: { id } }.

Levels

POST body: name, tier (≥1), min_points (≥0); max_points, benefits, brand. Level: id, name, tier, min_points, max_points, benefits, created_at.

Missions

GET query: page, limit, status (active/completed/expired/cancelled), type (daily/weekly/one_time/progressive/collaborative) → paginated. POST body: name, type; description, status, rules, rewards, max_completions_per_player, starts_at, ends_at, brand.

Badges

POST body: name; description, icon_url, criteria, rarity (common/rare/epic/legendary), brand.

Currencies (points)

POST body: name, symbol, type (real/virtual/bonus); brand.

GET /gamification/player/{playerId}

A player’s gamification profile: { player_id, current_level, point_balances[], active_missions[], badges_earned[] }.


Bonuses

GET /bonuses (brand-scoped) · POST /bonuses

POST body: name, type (welcome/reload/cashback/free_spins/ no_deposit/custom), value, value_type; wagering_requirement, config, expires_in_hours, active, brand.

GET/PATCH/DELETE /bonuses/{id}

Bonus: id, name, type, value, value_type, wagering_requirement, config, expires_in_hours, active, created_at.


Tournaments

GET /tournaments (brand-scoped) · POST /tournaments

POST body: name; description, status (draft/active/ended/cancelled), game_config, prize_pool, max_participants, starts_at, ends_at, brand.

GET/PATCH/DELETE /tournaments/{id}

Tournament: id, name, description, status, game_config, prize_pool, max_participants, starts_at, ends_at, created_at, updated_at.


Analytics & reports

Read-only. Brand-scoped via the optional brand hint.

GET /analytics/summary

Headline KPIs: players, active, revenue (GGR/NGR), deposits, avg LTV, lifecycle stage counts. → { data }.

GET /analytics/timeseries

Daily players + event volume. Query: from (ISO, default 30d ago), tz (IANA, default UTC). → { data[] }.

GET /analytics/revenue

Query: dimension (country/device/os/browser/platform/game/ provider/category/heatmap/bonus_roi), from, tz. → { data[] }.

GET /analytics/cohorts

Query: months (1–36, default 12). → { data }.

GET /reports/run

Ad-hoc metric × dimension. Query: metric (players/events/deposits/ wagered), dimension (day/week/status/country/game/event_type), from. → { data[] }.


Export (BI / warehouse sync)

GET /export/{dataset}

Bulk, cursor-paginated keyset export for incremental sync. datasetplayers/events/transactions. Query: cursor, limit (1–1000, default 200). Pass the returned next_cursor to continue. → { data[], next_cursor }.

GET /segments/{id}/export (above) is the segment-membership export.


SDK

The official @playlogiq/sdk wraps track(), trackBatch(), and identify() and sets x-api-key for you. Point it at your installation:

import { PlaylogiQ } from "@playlogiq/sdk"
 
const plq = new PlaylogiQ({
  apiKey: "plq_your_api_key",
  baseUrl: "https://crm-api.logiqdesk.com",  // your API host
})

See the Introduction for a quick-start, or the PAM Integration section for the ingestion contract.