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.
Schemas at a glance
| Schema | Who sends it | Natural key | What it feeds |
|---|---|---|---|
playerpal.provider.player_game_daily.v1 | Game provider | provider × player × game × day | Gameplay KPIs: active and new players, sessions, active time, retention, players at risk |
playerpal.provider.game_catalogue.v1 | Game provider | provider × game | Game names and attributes in game breakdowns and Explore game views |
playerpal.operator.player_commercial_daily.v1 | Operator | operator × player × day | Commercial KPIs: depositors, first-time deposits, deposits, GGR, NGR |
playerpal.affiliate.attribution.v1 | Operator | affiliate × player | Affiliate 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
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_partyisGAME_PROVIDERfor provider schemas andOPERATORfor operator and attribution schemas.source_idmust be your own party ID. Any other value returns403 source_id_must_be_authenticated_party.- A schema sent by the wrong party type returns
403 schema_not_allowed_for_party. batch_idis 1–128 characters ofA-Z a-z 0-9 _ . : -and is your idempotency key.period_startmust be beforeperiod_end. A batch holds at most 10,000 records.
schemastringrequiredProvider 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.v1schema_versionintegerrequiredSchema version; only1is accepted.batch_idstringrequiredYour id for this batch and its idempotency key, unique per party. Re-sending the samebatch_idwith identicalschemaandrecordsreturns the stored receipt; different content is409 idempotency_conflict.source_partystringrequiredGAME_PROVIDERfor provider schemas,OPERATORfor operator and attribution schemas.One of:GAME_PROVIDEROPERATORsource_idstringrequiredYour party public id. Must equal the authenticated party, otherwise403 source_id_must_be_authenticated_party.integration_idstring, nullableOptional integration public id, stored with the batch for provenance.period_startstring (date-time)requiredStart of the reporting period (ISO 8601, UTC).period_endstring (date-time)requiredEnd of the reporting period (ISO 8601, UTC). Must be afterperiod_start.generated_atstring (date-time)requiredWhen you produced the batch (ISO 8601, UTC).recordsarray of one ofrequiredRecord shape depends onschema.
{
"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": []
}
{
"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.
- player_game_daily
- game_catalogue
- player_commercial_daily
- attribution
| Field | Required | Notes |
|---|---|---|
date | Yes | UTC day, YYYY-MM-DD, inside the envelope period |
player_ref | Yes | Your provider alias pp_plr_prv_… |
game_ref | Yes | Your game ID, matching the game catalogue |
operator_ref | No | Operator ID pp_opr_… you are integrated with. Defaults to the alias's integration operator |
market | No | ISO 3166-1 alpha-2, upper case |
sessions, rounds, active_seconds | At least one of these or total_bet | Counts |
total_bet, total_win, jackpot_win | No | Non-negative amounts |
currency | When any amount is sent | EUR |
| Field | Required | Notes |
|---|---|---|
game_ref | Yes | Your game ID |
name | Yes | 1–200 characters |
category | No | SLOTS, LIVE, TABLE, CRASH, INSTANT or OTHER (default) |
release_date | No | YYYY-MM-DD |
rtp_percent | No | Greater than 0, at most 100 |
volatility | No | LOW, MEDIUM, HIGH or VERY_HIGH |
active | No | Defaults to true; only false marks a game inactive |
| Field | Required | Notes |
|---|---|---|
date | Yes | UTC day, YYYY-MM-DD, inside the envelope period |
player_ref | Yes | Your operator alias pp_plr_opr_… |
currency | Yes | EUR |
market | No | ISO 3166-1 alpha-2, upper case |
deposits_count, withdrawals_count | No | Counts |
deposits_amount, withdrawals_amount, stake_amount, win_amount, bonus_cost | No | Non-negative amounts |
ggr, ngr | No | Amounts, may be negative |
first_deposit | No | Boolean |
| Field | Required | Notes |
|---|---|---|
player_ref | Yes | Your operator alias pp_plr_opr_… |
affiliate_ref | Yes | Affiliate party ID pp_aff_… |
attribution_status | Yes | CLAIMED, OBSERVED or VERIFIED |
attribution_source | Yes | Short reference, same character rules as batch_id |
campaign_ref, click_ref | No | Your references |
attributed_at | No | ISO 8601 instant |
Only operators send attribution in this version, as the authoritative source.
{
"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"
}
{
"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.
You get 202 with records_accepted equal to the number of records you sent and replayed: false.
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
{
"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
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:
| Reason | Fix |
|---|---|
player_ref_invalid | The value is not a valid reference, for example an email address |
player_ref_unknown | Use an active alias PlayerPal issued to you |
date_invalid, date_outside_period | Use YYYY-MM-DD inside the envelope period |
currency_required, currency_unsupported | Send EUR with every amount |
activity_indicator_required | Send sessions, rounds, active_seconds or total_bet |
operator_ref_not_integrated | Use an operator you are integrated with, or omit it |
affiliate_ref_unknown | Use the affiliate's PlayerPal party ID |
duplicate_in_batch | Send each natural key once per batch |
<field>_invalid, record_not_object | Fix the malformed field or record |
batch_idstringrequiredThebatch_idyou sent.received_atstring (date-time)requiredWhen PlayerPal first received the batch (UTC).records_acceptedintegerrequiredRecords that passed validation and were stored.records_receivedintegerrequiredRecords in the batch.records_rejectedintegerrequiredRecords that failed validation; seerejections.rejectionsarray of objectrequiredPer-record rejections, capped at the first 500 (records_rejectedholds the full count).replayedbooleanrequiredtruewhen this receipt was read back from an earlier submission of the same batch, or fetched by ID.schemastringrequiredThe schema the batch was validated against.
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
{
"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 newbatch_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.
Sending the step 3 batch again returns 202 with replayed: true. Changing one record and keeping the batch_id returns 409.
{
"statusCode": 409,
"message": "idempotency_conflict",
"error": "Conflict"
}
{
"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
| Rule | Detail |
|---|---|
| Players | player_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 |
| Currency | EUR only in this version, because the conversion policy is not yet decided. Other currencies are rejected as currency_unsupported |
| Dates | Daily records use UTC days. A record's date must fall from the UTC day of period_start up to, but not including, period_end |
| Markets | ISO 3166-1 alpha-2 in upper case, for example MT |
Errors
| Status | Codes |
|---|---|
400 | envelope_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 |
401 | Signature or token problems. See Authentication |
403 | source_id_must_be_authenticated_party, schema_not_allowed_for_party, partner_scope_missing |
404 | batch_not_found (receipt lookup) |
409 | idempotency_conflict |
Every error code is listed in Idempotency and errors.