SAFE Reporting
SAFE (Spillemyndighedens Afrapporterings- og FeedbackSystem) is the Danish Gambling Authority's (DGA) mandatory reporting system. You must submit every game session, jackpot payout, and daily aggregate report to remain compliant with your DGA licence.
This document explains when to use each endpoint and how corrections work.
Prerequisites
Before any SAFE endpoint will accept requests, your client must be fully configured with all three of the following:
- License Certificate ID — issued by DGA after onboarding
- SAFE Website URL — your gambling site's registered URL
- SFTP provisioned — the export delivery account must be set up in the admin panel
If any of these are missing, all SAFE endpoints return 403 Forbidden. Your administrator can complete the setup from the admin panel under Clients → SAFE Setup.
Normal Daily Flow
During the day:
POST /api/safe/sessions ← report each game session as it ends
POST /api/safe/jackpots ← report each jackpot payout as it occurs
At end of day (before 02:00 UTC):
POST /api/safe/endofday ← submit aggregated daily totals
Sessions and jackpots can be submitted in real time throughout the day, or batched. The end-of-day report must be submitted once per calendar day, covering all sessions for that date. DGA's processing deadline is 02:00 UTC — the EOD must be submitted before this.
Sessions
Submit a session — POST /api/safe/sessions
Submit one record per completed game session.
| Field | Type | Description |
|---|---|---|
CasinoSessionId |
string | Your unique identifier for this session. Used for idempotency — re-submitting the same ID with identical data is safe. |
PlayerRef |
string | Player identifier from your platform. |
GameName |
string | Human-readable game name. |
GameCategory |
enum | Slots, Bingo, Roulette, Baccarat, PuntoBanco, Blackjack, Poker, Combination, Other |
Channel |
enum | Internet, Mobile, Retailer, SelfService, Other |
ProductCategory |
enum | e.g. CasinoSinglePlayer, CasinoMultiPlayer, BingoGame — see enum reference |
Currency |
string | ISO 4217, e.g. "DKK" |
StartedAt |
DateTimeOffset | Session start (UTC) |
EndedAt |
DateTimeOffset | Session end (UTC). Determines which business day this session belongs to for EOD purposes. |
NumberOfGames |
long | Number of rounds/games played within the session |
OpenNetwork |
bool | true for Internet/open network, false for closed/retail network |
StakeGames |
decimal | Total stake placed during the session |
WinningsGames |
decimal | Total winnings paid out during the session |
CommissionRake |
decimal | Commission or rake taken |
JackpotContributions |
array | Optional. List of { JackpotId, Amount } for jackpot pool contributions. |
RngId |
string | RNG identifier |
RngSoftwareId |
string | RNG software version |
Idempotency: Submitting the same CasinoSessionId twice with identical data returns 200 OK without creating a duplicate. If the same ID is submitted with different data, you receive 409 Conflict with the existing record returned so you can compare.
Cancel a session — `PUT /api/safe/sessions/
Use this when a player exercises a right of withdrawal, or when a game error requires voiding a session after it has already been reported.
| Field | Type | Description |
|---|---|---|
CasinoSessionId |
string | Your external session identifier |
Reason |
string | Optional. Free-text reason for cancellation. |
How cancellations affect EOD:
- If you cancel a session on the same day it was played, it is excluded from that day's EOD totals.
- If you cancel a session after the day's EOD has already been submitted (i.e. on a subsequent day), the original session remains in the prior day's EOD — that report stands unchanged. The cancellation is attributed to the current day: it appears in today's session file and is subtracted from today's EOD totals.
Cancellation is idempotent — cancelling an already-cancelled session returns 200 OK.
Jackpots
Register a jackpot — POST /api/safe/jackpots
Submit one record per jackpot payout event.
| Field | Type | Description |
|---|---|---|
CasinoJackpotId |
string | Your unique identifier for this payout event. |
Currency |
string | ISO 4217, e.g. "DKK" |
ProductCategory |
enum | Product category the jackpot originated from |
OccurredAt |
DateTimeOffset | When the jackpot was won (UTC) |
TotalAmount |
decimal | Total amount paid to all winners combined |
Commission |
decimal? | Commission/rake on the jackpot, if applicable |
Winners |
array | Required, minimum 1. Each entry: { PlayerId, Amount } |
Idempotency: Same behaviour as sessions — identical re-submission is safe, conflicting data returns 409 Conflict.
Correct a jackpot — `PUT /api/safe/jackpots/
Use this if a jackpot payout was reported with an incorrect amount or winner details and you need to submit a correction.
| Field | Type | Description |
|---|---|---|
CasinoId |
string | Must match the original CasinoJackpotId |
TotalAmount |
decimal | Corrected total amount |
Commission |
decimal? | Corrected commission, or null |
Winners |
array | Corrected winner list |
Reason |
string | Optional. Reason for the correction (kept for your audit trail). |
This creates a SAFE replacement file referencing the original, which DGA uses to supersede the previously submitted jackpot data. No DGA coordination is required for jackpot corrections — the replacement mechanism is self-contained.
End of Day
Submit EOD — POST /api/safe/endofday
Submit once per calendar day, covering all sessions for that date. Must be submitted before 02:00 UTC on the following day.
| Field | Type | Description |
|---|---|---|
Date |
DateOnly | The business date being reported (e.g. 2025-04-01) |
ClientTotals |
array | One entry per (ProductCategory, Currency) combination active that day |
Each entry in ClientTotals:
| Field | Type | Description |
|---|---|---|
ProductCategory |
enum | e.g. CasinoSinglePlayer |
Currency |
string | ISO 4217 |
TotalStake |
decimal | Sum of all session stakes for this product/currency |
TotalWin |
decimal | Sum of all session winnings for this product/currency |
TotalJackpotContribution |
decimal | Sum of all jackpot contributions from sessions |
TotalCommissionRake |
decimal | Sum of all commission/rake |
TotalNumberOfGames |
long | Sum of all games played |
Validation: The API independently calculates the expected totals from the sessions you have submitted and compares them against your submitted figures. If there is any discrepancy, the request is rejected with 400 Bad Request and a detailed breakdown of each mismatch is returned. Ensure your totals exactly match your submitted session data.
Cross-day cancellations: If you cancelled any sessions today that were originally played on a prior day, their amounts are automatically included as a negative offset in today's totals calculation. Your submitted
ClientTotalsmust account for these as well.
Correct an EOD — `PUT /api/safe/endofday/
⚠️ This operation must be agreed with DGA before use. EOD replacements are reserved for situations where data was incorrectly reported (rapporteringsfejl) — not for normal game-change cancellations. Contact the Danish Gambling Authority and obtain their approval before submitting an EOD correction.
Use this only after receiving DGA approval. Provide the corrected totals in the same format as the original submission. The API validates the new totals against the current session data and — if they match — makes a replacement EOD file available to DGA with a reference to the original.
Error Reference
| Status | Meaning |
|---|---|
400 Bad Request |
Invalid input, or EOD totals do not match session data. The response body includes a detailed diffs array showing each mismatched field. |
403 Forbidden |
Your client is not configured for SAFE reporting. Contact Fortuna. |
404 Not Found |
The session, jackpot, or EOD report referenced does not exist. |
409 Conflict |
A record with the same external ID already exists with different data. The response includes the existing record so you can compare. |
Supplier Aggregation
If your integration is set up in supplier aggregation mode (multiple game suppliers reporting under a shared DGA licence), each supplier submits their own sessions, jackpots, and EOD independently using their own API key. Fortuna automatically aggregates all supplier EODs into a single master file for DGA each night — no additional steps are required from the suppliers.
When aggregation is blocked
The master EOD file is only generated when all suppliers have submitted their EOD for the day and all submitted totals are consistent with the underlying session data. If any supplier has not submitted their EOD, or if a discrepancy is detected in one supplier's data, the master file is held back — it will not be made available to DGA until the issue is resolved.
When this happens:
- The ops email address configured on your account receives an alert listing which supplier(s) caused the problem and, for discrepancies, the exact fields and amounts that do not reconcile.
- A full breakdown is available in the admin panel under your SAFE configuration's aggregation history, showing the run status, which suppliers were affected, and the per-field delta for any discrepancy.
- The aggregation job retries automatically the following night. If you have confirmed the issue is resolved and want to make the file available to DGA sooner, you can retrigger the aggregation yourself directly from the admin panel — open the SAFE configuration, go to Supplier Management → Aggregation Runs, and click the retrigger icon (↺) next to the failed run.
Resolving a blocked aggregation
Missing EOD: The flagged supplier(s) need to submit their EOD for the affected date via POST /api/safe/endofday before the next aggregation run.
Discrepancy: The supplier's submitted EOD totals do not match the sessions that were reported. The supplier should review their session data for the affected date and resubmit a corrected EOD using PUT /api/safe/endofday/{endOfDayId}. Note that correcting an EOD after the fact may require prior coordination with DGA — see Correct an EOD above.