Native ingestion (/ingest)
Some PAM / tracking systems can only POST their own payload to a URL — they
can’t reshape the body to our canonical { player_id, event_type, … } envelope,
and they expect a specific response. POST /api/v1/ingest is for exactly that:
the PAM sends its native shape verbatim, and a per-brand envelope mapping
tells us how to read it.
If you control the payload, prefer /events (our
canonical shape). Use /ingest when the sender’s format is fixed.
Endpoint
POST https://crm-api.logiqdesk.com/api/v1/ingest?api_key=plq_…
Content-Type: application/jsonAuth is the operator API key, sent as ?api_key= (or Authorization: Bearer, or
x-api-key) — see Authentication. A brand-pinned
key is the brand boundary. Accepts a single object, an array, or
{ "events": [ … ] } (max 100 per request).
Response contract
Unlike /events, this endpoint answers in a compact code form (so a PAM that
checks a status code / code field is satisfied):
| Outcome | HTTP | Body |
|---|---|---|
| Accepted | 200 | { "code": 0 } |
| Bad data / not configured | 200 | { "code": 1, "error": "…" } |
| Rate limited | 429 | back off and retry (Retry-After) |
| Our failure | 5xx | { "code": 1, "error": "…" } — safe to retry |
A 200 { "code": 1 } is a data problem (won’t succeed on retry); a 5xx is
transient (retry). Always attach an idempotency key (mapped from your
transaction_id, etc.) so retries never double-count.
The envelope mapping
/ingest requires the brand’s mapping to include an envelope section — how
to read the top-level fields out of the PAM’s shape — on top of the normal
mapping engine (event-type map, field maps, unmapped
policy). The envelope is applied first, before canonical processing:
"envelope": {
"playerId": "user_id", // → player_id (coerced to string)
"eventType": "type", // → event_type
"timestamp": ["timestamp", "registration_date"], // first present wins → ISO
"eventId": "transaction_id" // → idempotency key
}Refining the event type by a field
A PAM often sends one type with a discriminator field. Refine the canonical
type conditionally (applied after eventTypeMap, in order):
"eventTypeRefine": [
{ "whenType": "bet", "field": "product", "equals": "sport", "set": "bet_sport" },
{ "whenType": "bet", "field": "source", "equals": "bonus", "set": "bet_bonus" }
]Assume consent at registration
If the operator’s registration flow already captures marketing consent (so every player is legally consented), set:
"ingest": { "assumeConsent": true }Players are then marked consented on their first registration event only —
never on replay/update, so a later manual opt-out is never silently re-enabled.
Worked example (native type/user_id shape)
A PAM posts a deposit in its own shape:
{ "type": "deposit", "skin_id": 69, "user_id": 7177793,
"amount": 50.00, "currency": "USD",
"transaction_id": "TXN123456", "timestamp": "2025-02-19T14:30:00Z" }With the envelope above + "eventTypeMap": { "deposit": "deposit" } and a
payload field map (amount → amount, currency → currency), ingestion
produces a canonical deposit for player 7177793, deduped on TXN123456, and
replies { "code": 0 }. The worker then folds it into the player’s deposit
totals. The raw body is always retained under _raw.
Static profile fields (name, DOB, language) only arrive in the event that carries them (usually
registration). Players already ingested before those fields are added won’t back-fill automatically — ask the PAM for a one-time replay ofregistration(or a profile export) for existing users. Our ingestion upserts by the player id, so re-sending is safe (no duplicates).