Skip to main content
AvailableVerified against 76c1f85Markdown

Send daily facts

Fact ingest is how you give PlayerPal the daily figures behind your analytics: gameplay per player and game from studios, commercial figures per player from operators, and affiliate attribution. You send facts; PlayerPal computes every KPI from them, so all partners see metrics defined the same way. Ingest is batch-first: one delivery per day, or per period, is enough.

You will
  • Build a batch envelope for your schema
  • Send and sign a batch of daily facts
  • Read the receipt and fix rejected records
  • Resend and correct data without duplicates
You need
  • A key ID and secret, or an OAuth client, with the ingest.write scope
  • Linked players and the aliases PlayerPal issued to you
  • Your party ID
Time
About 20 minutes
For
Game providers and operators

Schemas at a glance​

SchemaWho sends itNatural keyWhat it feeds
playerpal.provider.player_game_daily.v1Game providerprovider × player × game × dayGameplay KPIs: active and new players, sessions, active time, retention, players at risk
playerpal.provider.game_catalogue.v1Game providerprovider × gameGame names and attributes in game breakdowns and Explore game views
playerpal.operator.player_commercial_daily.v1Operatoroperator × player × dayCommercial KPIs: depositors, first-time deposits, deposits, GGR, NGR
playerpal.affiliate.attribution.v1Operatoraffiliate × playerAffiliate views, which count only VERIFIED attributions

A KPI whose feed you have not delivered shows requires_feed in the overview, never a zero. Points, rewards, redemptions and missions are native to PlayerPal, so you never send them. Definitions are in Metrics.

1Build the envelope

POST/v1/ingestSubmit a fact batchingest.writeReference →

A batch is one envelope with records of one schema. PlayerPal checks the envelope first: if it is invalid, the request fails with 400 and nothing is stored.

  • source_party is GAME_PROVIDER for provider schemas and OPERATOR for operator and attribution schemas.
  • source_id must be your own party ID. Any other value returns 403 source_id_must_be_authenticated_party.
  • A schema sent by the wrong party type returns 403 schema_not_allowed_for_party.
  • batch_id is 1–128 characters of A-Z a-z 0-9 _ . : - and is your idempotency key.
  • period_start must be before period_end. A batch holds at most 10,000 records.
Request body
  • schemastringrequired
    Provider schemas require source_party GAME_PROVIDER and a PROVIDER party; operator and attribution schemas require OPERATOR.
    One of: playerpal.provider.player_game_daily.v1playerpal.provider.game_catalogue.v1playerpal.operator.player_commercial_daily.v1playerpal.affiliate.attribution.v1
  • schema_versionintegerrequired
    Schema version; only 1 is accepted.
  • batch_idstringrequired
    Your id for this batch and its idempotency key, unique per party. Re-sending the same batch_id with identical schema and records returns the stored receipt; different content is 409 idempotency_conflict.
  • source_partystringrequired
    GAME_PROVIDER for provider schemas, OPERATOR for operator and attribution schemas.
    One of: GAME_PROVIDEROPERATOR
  • source_idstringrequired
    Your party public id. Must equal the authenticated party, otherwise 403 source_id_must_be_authenticated_party.
  • integration_idstring, nullable
    Optional integration public id, stored with the batch for provenance.
  • period_startstring (date-time)required
    Start of the reporting period (ISO 8601, UTC).
  • period_endstring (date-time)required
    End of the reporting period (ISO 8601, UTC). Must be after period_start.
  • generated_atstring (date-time)required
    When you produced the batch (ISO 8601, UTC).
  • recordsarray of one ofrequired
    Record shape depends on schema.
Envelope
{
"schema": "playerpal.provider.player_game_daily.v1",
"schema_version": 1,
"batch_id": "daily-2026-10-12-0001",
"source_party": "GAME_PROVIDER",
"source_id": "pp_prv_XXXX",
"integration_id": null,
"period_start": "2026-10-12T00:00:00Z",
"period_end": "2026-10-13T00:00:00Z",
"generated_at": "2026-10-13T01:15:00Z",
"records": []
}
Invalid envelope400
{
"statusCode": 400,
"message": "period_invalid",
"error": "Bad Request"
}

2Add records for your schema

After the envelope, every record is validated on its own. Amounts are decimal strings (preferred) or numbers with at most 2 decimal places; counts are non-negative integers. References use only letters, digits and _ . : -, up to 128 characters.

FieldRequiredNotes
dateYesUTC day, YYYY-MM-DD, inside the envelope period
player_refYesYour provider alias pp_plr_prv_…
game_refYesYour game ID, matching the game catalogue
operator_refNoOperator ID pp_opr_… you are integrated with. Defaults to the alias's integration operator
marketNoISO 3166-1 alpha-2, upper case
sessions, rounds, active_secondsAt least one of these or total_betCounts
total_bet, total_win, jackpot_winNoNon-negative amounts
currencyWhen any amount is sentEUR
player_game_daily record
{
"date": "2026-10-12",
"player_ref": "pp_plr_prv_XXXX",
"game_ref": "your-game-ref",
"market": "MT",
"currency": "EUR",
"sessions": 3,
"rounds": 120,
"total_bet": "60.00",
"total_win": "52.40"
}
player_commercial_daily record
{
"date": "2026-10-12",
"player_ref": "pp_plr_opr_XXXX",
"currency": "EUR",
"deposits_count": 1,
"deposits_amount": "50.00",
"ggr": "-12.50",
"first_deposit": false
}

3Sign and send the batch

Sign the request with PP-HMAC-SHA256, as in the Quickstart, or send an OAuth bearer token. Sign the exact bytes you send: re-serialising the JSON after signing breaks the signature.

PlayerPal answers 202 Accepted with a receipt. One bad record never rejects the batch: it is listed in rejections and the rest are stored.

Checkpoint

You get 202 with records_accepted equal to the number of records you sent and replayed: false.

POST /v1/ingest
playerpal.sh
BODY='{"schema":"playerpal.provider.player_game_daily.v1","schema_version":1,"batch_id":"daily-2026-10-12-0001","source_party":"GAME_PROVIDER","source_id":"pp_prv_XXXX","period_start":"2026-10-12T00:00:00Z","period_end":"2026-10-13T00:00:00Z","generated_at":"2026-10-13T01:15:00Z","records":[{"date":"2026-10-12","player_ref":"pp_plr_prv_XXXX","game_ref":"your-game-ref","currency":"EUR","rounds":120,"total_bet":"60.00","total_win":"52.40"}]}'
HEADERS=(); while IFS= read -r h; do HEADERS+=("$h"); done < <(sign POST /v1/ingest "" "$BODY")
curl -sS -X POST "$API/v1/ingest" \
-H "${HEADERS[0]}" -H "${HEADERS[1]}" -H "${HEADERS[2]}" \
-H 'Content-Type: application/json' \
--data-binary "$BODY" # 202 and the batch receipt
Response202
{
"batch_id": "daily-2026-10-12-0001",
"schema": "playerpal.provider.player_game_daily.v1",
"received_at": "2026-10-13T01:15:02.000Z",
"records_received": 1,
"records_accepted": 1,
"records_rejected": 0,
"rejections": [],
"replayed": false
}

4Read receipts

GET/v1/ingest/batches/{batchId}Get a batch receiptingest.writeReference →

The receipt is stored with the batch, so you can fetch it later by batch_id as often as you need. A fetched receipt always has replayed: true. An unknown batch_id returns 404 batch_not_found.

index in each rejection points into your records array. The receipt lists at most 500 rejections; the counts are always complete. Common reasons:

ReasonFix
player_ref_invalidThe value is not a valid reference, for example an email address
player_ref_unknownUse an active alias PlayerPal issued to you
date_invalid, date_outside_periodUse YYYY-MM-DD inside the envelope period
currency_required, currency_unsupportedSend EUR with every amount
activity_indicator_requiredSend sessions, rounds, active_seconds or total_bet
operator_ref_not_integratedUse an operator you are integrated with, or omit it
affiliate_ref_unknownUse the affiliate's PlayerPal party ID
duplicate_in_batchSend each natural key once per batch
<field>_invalid, record_not_objectFix the malformed field or record
Response fields
  • batch_idstringrequired
    The batch_id you sent.
  • received_atstring (date-time)required
    When PlayerPal first received the batch (UTC).
  • records_acceptedintegerrequired
    Records that passed validation and were stored.
  • records_receivedintegerrequired
    Records in the batch.
  • records_rejectedintegerrequired
    Records that failed validation; see rejections.
  • rejectionsarray of objectrequired
    Per-record rejections, capped at the first 500 (records_rejected holds the full count).
  • replayedbooleanrequired
    true when this receipt was read back from an earlier submission of the same batch, or fetched by ID.
  • schemastringrequired
    The schema the batch was validated against.
GET /v1/ingest/batches/{batchId}
playerpal.sh
P=/v1/ingest/batches/daily-2026-10-12-0001
HEADERS=(); while IFS= read -r h; do HEADERS+=("$h"); done < <(sign GET "$P" "" "")
curl -sS "$API$P" -H "${HEADERS[0]}" -H "${HEADERS[1]}" -H "${HEADERS[2]}"
# 200 and the stored receipt, replayed: true
A receipt with rejections200
{
"batch_id": "pgd-2026-10-14",
"schema": "playerpal.provider.player_game_daily.v1",
"received_at": "2026-10-15T01:15:02.311Z",
"records_received": 1200,
"records_accepted": 1197,
"records_rejected": 3,
"rejections": [
{ "index": 14, "reason": "player_ref_unknown" },
{ "index": 220, "reason": "date_outside_period" },
{ "index": 301, "reason": "duplicate_in_batch" }
],
"replayed": true
}

5Resend and correct data

batch_id makes retries safe. PlayerPal compares the schema and records of a repeated batch_id with the stored batch.

  • Same records: you get the original receipt back with replayed: true, and nothing is stored twice. Retry a timed-out call with the identical batch.
  • Different records: 409 idempotency_conflict. New data always needs a new batch_id.
  • Inside one batch: the last record for each natural key wins; earlier ones are rejected as duplicate_in_batch.

To correct data, send the corrected facts in a new batch. Records replace the stored fact for the same natural key, and PlayerPal keeps a revision count and the time it received the correction. Resend the full record, not only the changed fields.

Checkpoint

Sending the step 3 batch again returns 202 with replayed: true. Changing one record and keeping the batch_id returns 409.

Same batch_id, different records409
{
"statusCode": 409,
"message": "idempotency_conflict",
"error": "Conflict"
}
Correction batch
{
"schema": "playerpal.provider.player_game_daily.v1",
"schema_version": 1,
"batch_id": "daily-2026-10-12-0001-fix1",
"source_party": "GAME_PROVIDER",
"source_id": "pp_prv_XXXX",
"period_start": "2026-10-12T00:00:00Z",
"period_end": "2026-10-13T00:00:00Z",
"generated_at": "2026-10-14T09:00:00Z",
"records": [
{ "date": "2026-10-12", "player_ref": "pp_plr_prv_XXXX",
"game_ref": "your-game-ref", "currency": "EUR",
"rounds": 124, "total_bet": "62.00", "total_win": "52.40" }
]
}

Data rules​

RuleDetail
Playersplayer_ref must be an alias PlayerPal issued to you: pp_plr_prv_… through your integrations if you are a studio, pp_plr_opr_… if you are an operator. An alias for another audience or a revoked alias is rejected as player_ref_unknown. An email address or phone number fails as player_ref_invalid before it is ever matched
CurrencyEUR only in this version, because the conversion policy is not yet decided. Other currencies are rejected as currency_unsupported
DatesDaily records use UTC days. A record's date must fall from the UTC day of period_start up to, but not including, period_end
MarketsISO 3166-1 alpha-2 in upper case, for example MT

Errors​

StatusCodes
400envelope_required, schema_unsupported, schema_version_unsupported, batch_id_invalid, source_party_not_allowed_for_schema, source_id_required, period_invalid, records_required, too_many_records
401Signature or token problems. See Authentication
403source_id_must_be_authenticated_party, schema_not_allowed_for_party, partner_scope_missing
404batch_not_found (receipt lookup)
409idempotency_conflict

Every error code is listed in Idempotency and errors.