Fortuna Compliance API

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 ClientTotals must 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.