A trading venue that must report each executed trade and each bust to its regulator can have the platform do it. The platform renders one FIX trade capture report, message type 35=AE by default, for each trade event in the trade journal of a published reporting obligation. FIX, the Financial Information eXchange protocol, is the message standard trading venues and regulators use. The platform queues each report and sends the queue over a FIX session that it opens to the regulator, then records the regulator's answer on the report. A session runs only to an endpoint the deployment allows. When no session can run, the reports stay queued and the organisation files the trade journal export through its own channel.
You configure the message layout and the session through the Platform API. The Console shows the queue and the session state.
The obligation version holds the message layout. The FIX tags for trade and bust reports are written into the obligation version, so a change to a regulator's tag set needs a new version and no platform release. A report keeps the version it was queued under.
Two groups act on trade reports. A Reporting reader reads the queue and the session state: an organisation owner, a platform administrator, or a member whose acting wallet holds the Permission manager, Auditor, Compliance manager or System manager role on the organisation's system. An organisation owner or a platform administrator also records an outcome by hand, corrects a rejected report, and drafts and publishes the obligation that carries the layout and the session.
What the platform sends
A report is one message about one event. Its identity is the trade and the event kind, trade or bust, joined with a colon, so a trade that is later busted produces one report of each kind: trade-1:trade and trade-1:bust.
Each message follows the version's layout. The body starts with the message type in tag 35 and continues with each mapped tag in layout order. The platform then adds the FIX version string, called the BeginString, in tag 8, the body length in tag 9 and the checksum in tag 10. When the session sends the message it adds the sequence number, the sender and target identifiers, called CompIDs, and the sending time. A version that carries no layout uses this default:
The default BeginString is FIXT.1.1. A layout can name a separate message type for a bust. Without one, a bust report uses the trade message type and differs from the trade report in its report ID and its fields.
A layout reads from the fields each trade journal row carries:
A mapping whose value is empty is left out of the message unless the mapping is required. A required tag with an empty value, a value outside printable ASCII, or a value that breaks a validation rule stops the render, and the report is not queued.
When a report is queued
The platform checks for trade changes 15 seconds after its previous check ends. Each check covers every published obligation that is not disabled and whose report set includes the trade journal. It queues reports for such an obligation whether or not the obligation configures a regulator session. A change is a new trade, a bust, or a later update to a trade, for example its settlement. The platform reads the changes in the order the trades last changed and queues one report for each trade and each bust under the obligation's published version.
An obligation queues each trade and each bust once, across all its versions. A later update to a trade it already queued, other than its bust, adds no report. If that report has never been sent or corrected and still waits under the published version, the platform renders its body again from the changed trade. Every other report keeps its body, and only its source time moves.
A new version resumes after the last trade change any earlier version of the same obligation reached. Reports an earlier version queued keep the body that version rendered. The organisation's session sends the ones still waiting, and a regulator answer to any of them moves that report.
Each obligation tracks the last trade change it queued separately, so two obligations with the trade journal each queue a report of every trade and bust. The organisation's one session sends every queued report. An answer moves only the report of the obligation that runs the session, and the other obligation's report stays submitted. When both obligations configure a session, no session starts.
A published layout can still fail to render one trade, for example when a required tag maps to a field that trade leaves empty. Queueing for that obligation then stops at that trade, so no later trade or bust is queued ahead of it. The session still sends the reports queued before it. The platform logs “Queueing transaction reports stopped for this obligation” with the obligation, its version and the reason, for example FIX tag 571 is required, and logs it again on every check until the trade renders. The log does not name the trade. The queue count, the oldest-report age and the Transaction alert do not show the stop, because nothing new is queued. Other obligations and organisations keep queueing.
The platform writes that message to its logs only, and no screen or alert shows the stop. To restart queueing for that obligation, an organisation owner or a platform administrator changes the message layout in the obligation's draft through the Platform API so the logged reason no longer applies, for example by mapping the required tag to a field every trade carries, then publishes the draft on Obligations. The Console keeps the message layout but does not edit it. The new version resumes at that trade. Queueing also resumes without a new version when a later change to that trade gives it the missing value. The trade then counts as changed at that moment, so trades and busts that changed while queueing was stopped are queued before it.
A report keeps:
Configure the message layout for an obligation version
The message layout sits on the obligation version beside the report set and the delivery settings. It names the BeginString, defaulting to FIXT.1.1, the message type for a trade, an optional separate message type for a bust, the field mappings, the validation rules and, optionally, how the regulator answers.
Edit the layout and the session through the Platform API. The Console's obligation editor has no field for either: it opens a draft that carries the trade journal with “Trade journal” selected and keeps the layout and the session when you save. Create or replace the draft with POST /api/ or PUT /api/, then publish it with POST /api/. The Platform API reference carries the request shapes.
A field mapping takes a tag number, a source field, an optional expression and a required flag. The source field is one of the trade journal fields in What the platform sends. An expression substitutes named source fields into a constant, as ${tradeId}-${eventType} does. A tag can be mapped once. The session envelope tags 8, 9, 10, 34, 35, 49, 52 and 56 cannot be mapped, because the platform writes them.
A validation rule names a tag and one of three kinds: required, a pattern, or a comma-separated list of allowed values. Each carries the message a refusal shows. A rule must name a tag that a mapping carries, since a rule on any other tag could never be checked.
Publication refuses a version whose layout breaks any of these rules and names the message layout as the rule that failed. Publication also refuses a version that configures a regulator session while its report set leaves out the trade journal, because the session sends trade journal reports only.
How the answer is read
By default the platform reads a TradeCaptureReportAck, 35=AR, and takes the report ID from tag 571, the status from tag 939, the regulator's reference from tag 818, and the reason from tags 751 and 58. Status 0 accepts the report and status 1 rejects it.
A version can carry its own answer layout instead: the answer's message type, the tag that echoes the report ID, the status values that accept and reject, the tag carrying the regulator's reference, and the tags that explain a rejection. An answer with no report ID, an acceptance with no reference, and a status that is neither an acceptance nor a rejection are unreadable, so each one lands on the exceptions list and changes no report. A rejection with no reason text is recorded as “Rejected without a stated reason”.
How a session sends the queue
The platform runs one FIX session for each organisation whose published trade journal version configures one, and the platform opens the connection to the regulator. The version names:
- the regulator's host and port, whether the connection uses TLS, and the server name to present, which defaults to the host
- the sender and target CompIDs
- the heartbeat interval, from 5 to 300 seconds, defaulting to 30
- whether Logon resets the sequence numbers, which defaults to no
- an optional credential slot
- an optional weekly schedule of UTC days and a daily window, where a window whose end is before its start runs past midnight
- how long the session's messages are kept,
messageRetentionClass, which defaults toindefinite - the time zone whose calendar day closes a trading day for the reconciliation,
tradingDayTimeZone, which defaults toUTC - an optional test environment,
testProfile, with the same fields as the session, for certification
Without a schedule the session stays up. The version never holds a username or a password. A credential slot names two entries in the deployment's secret store, reporting/ and reporting/, which the platform reads when the session connects.
Every 30 seconds a supervisor compares the published versions with the sessions it runs. Three checks run before the supervisor connects. A session that fails one is logged once and not started:
- The endpoint is on the allow list. The deployment lists the regulator endpoints it allows as
host:portentries inregulator, set through theReporting. allowed Endpoints REGULATOR_environment variable. A session to any other endpoint is not started. With the list empty, no regulator session runs.REPORTING_ ALLOWED_ ENDPOINTS - The organisation runs one session. When more than one of its published trade journal versions configures one, none starts until one remains.
- The FIX session supports the layout's BeginString.
A session sends queued reports oldest first, up to 100 at a time. Each report takes the next sequence number from the session's stored counter and gets an entry in the sent log that keeps the exact bytes sent, and the report moves to submitted with its send time. A restart therefore continues the sequence without a gap. When the regulator asks for messages again, the session replays the bytes the regulator first received, marked as a possible duplicate with PossDupFlag and carrying the original sending time. A Logon with sequence reset, or a reset the regulator asks for, starts the sequence numbers again from 1 and puts every report the regulator has not confirmed back in the queue.
The sent log holds the bytes of each report. The session's message log, described in Keep the session's messages, holds every message of the session and is readable through the Platform API.
A dropped connection reconnects after one second, doubling the wait up to one minute. The session stops at the end of its schedule window, when its version's configuration changes, or when the version stops being published, and the next supervisor pass starts it again when it should run. Some faults hold the session instead of reconnecting it: the regulator logs on or answers with other CompIDs, sends a sequence number lower than expected, or logs out with a status that asks the initiator to stop. A held session stays down until its configuration changes, its schedule window closes and opens again, or the platform service restarts.
When a report moves and when it does not
An acceptance or rejection moves only the report it names, and only from submitted. A repeated acceptance with the same reference, or a repeated rejection with the same reason, leaves the report as it is. The regulator can also refuse a message at session level, with a FIX Reject or BusinessMessageReject, which moves the report sent under that sequence number.
The two kinds of answer are recorded in opposite orders. An answer to a report is applied to the report before the session records that it received it, so a crash between the two applies the same answer again and leaves the report as it is. A session-level rejection is recorded as received before it moves the report, so a crash between the two can leave the report submitted. Record the rejection by hand in that case.
An answer that cannot move a report is kept with its raw message on the organisation's exceptions list. The Filings screen shows each one with its reason:
Correct a rejected report or record an outcome by hand
An organisation owner or a platform administrator records each change with POST /api/. The route returns the report after the change, and the request audit records the actor and the transition. The Platform API reference carries the request shape.
A rejected report is fixed by resubmitting a corrected source row that names the same trade and event kind. The platform renders the body again from the version the report was queued under, keeps the report ID, increments the attempt and puts the report back in the queue. The platform checks only that the row names the same trade and event kind. It does not compare the other fields with the trade journal, so the corrected report carries the values the caller sends.
The earlier rejection reason, send time and reference are cleared from the report, so the report holds no history of its corrections. The audit trail holds each transition, and the session's sent log keeps the bytes of each attempt sent.
A transition from any other state is refused with the state the report is in. The transition route identifies a report by its platform ID, which you read from the report list.
Find a report
GET /api/ lists the organisation's reports, newest queued first. Each row carries the platform id that the transition route takes, the regulator-facing reportId, the tradeId, the state, the attempt, the rendered body and the rejection reason. Any Reporting reader can call it, the same audience as the queue status.
meta.total counts the reports that match. The list returns up to 50 reports per page, or the number set in page[limit] up to 200. When more match, meta.nextCursor holds a cursor: send it as page[cursor] with the same filters to read the next page. A cursor issued for other filters or another organisation is refused. To correct a rejection, list with filter[state]=rejected, then send resubmit for the row's id.
Fall back to the file
The trade journal is also a report in an ordinary filing. A version whose report set includes it produces the journal as a file in the version's output format, through the same generation, review and approval steps as any other filing on Filings. When no regulator session can run, an organisation owner or a platform administrator files that export through the organisation's own channel and records each outcome on the transition route: submit for a report filed outside the platform, then acknowledge with the regulator's reference or reject with the reason. The report then reads exactly as the session would have left it, and nothing on the report marks the outcome as recorded by hand. The audit trail holds the actor and the transition. The acknowledge and reject transitions also apply to a report the session sent, so check the trail before you rely on a report's answer as the regulator's own.
Reconcile each trading day
Each day the platform compares what the venue executed with what the regulator acknowledged. A pass runs every 5 minutes for each organisation whose published trade journal version configures a session. It reconciles every trading day of the last 14 days that has closed, plus a grace hour, in the time zone the session names in tradingDayTimeZone, which defaults to UTC. A day that still has breaks is reconciled again on each pass.
Each trade and each bust of the day lands in one class:
Every class but acknowledged is a break. The platform keeps the result as a numbered version of the day. It stores a new version only when the result differs from the day's latest, so the version number counts real changes, and a stored version is never edited. A day with breaks raises one critical operations case for that day, and the case closes on the pass that finds the day balanced. The same breaks move the Transaction alert, which fires when a break stays open for 15 minutes.
A Reporting reader reads the versions with GET /api/ and the lines of one version, filtered by class, with its lines route. A report of kind regulator-reconciliation exports the versions as a file. The Platform API reference carries the request shapes.
Keep the session's messages
The platform records every message of the session in both directions, administrative messages included, such as Logon, Heartbeat, Resend Request and Sequence Reset. Each entry keeps the raw frame, the sequence number, the sequence reset it belongs to, the message type, the possible-duplicate flag, the sending time and the time the platform recorded it. The Logon password and a new password in the frame are replaced before the frame is stored, so the stored frame's body length and checksum no longer match its content.
How long the messages stay is configuration. The session's messageRetentionClass is five-years, seven-years, ten-years or indefinite, and a session that sets none keeps every message. The platform carries no regulator's retention period, so set the class to the period your regulator requires. A sweep erases the messages older than their class, except those a legal hold covers.
A duty manager places a legal hold with POST /api/, for a period or for every message when the period is left out, and releases it with a PATCH on the hold. A released hold stays on record. The next sweep after a release erases the messages past their class that no other hold covers.
A Reporting reader lists the messages of a period with GET /api/, which answers the last 24 hours when no period is given, and lists the holds with GET /api/. A report of kind regulator-messages exports the messages of a date range. Wide PDF reports group the columns into separate tables and repeat a row number so you can follow the same message across each group. CSV exports keep every column on one row.
Show that the session is monitored
GET /api/ takes a period and answers with the proof an auditor asks for:
- each session's uptime in seconds and as a percentage of the period, and how many times it logged on and off
- the logon and logout history, with the reason recorded for each
- the messages the regulator sent and the platform sent, the session-level rejects, the resend requests and the sequence resets
- the reports in each state, the age of the oldest queued report, the longest wait, and how many reports waited longer than 15 minutes, counted until the report was sent or the period ended
- the alerts the reconciliation raised
The history includes every logon, logout, delay breach and reconciliation alert in the requested period. A report of kind regulator-monitoring exports the same evidence across as many pages as the period needs. The Filings screen shows the last 24 hours.
Certify the session
The regulator's specification and test endpoint are not part of the platform. The session configuration carries an optional testProfile with the same fields as the session, and the obligation version carries the message layout, so both come from the regulator when it supplies them.
bun run certify in the reporting app package runs four cases: a new report, a bust report, a rejected report that is corrected and sent again, and a dropped connection followed by a resend. -- runs them against a FIX acceptor the script starts, which proves the platform's session logic and not the regulator's. -- dials the testProfile of the session configuration named by --config, reading the test credentials from FSRA_TEST_USERNAME and FSRA_TEST_PASSWORD. It also needs --reject-source, a source row the test environment rejects. --spec names the message layout and defaults to the platform's default layout. The test sender and target CompIDs must each differ from the live session’s CompIDs. The script keeps the session sequence numbers across the four cases. The disconnect case restarts the client after four acknowledgements and requires a second accepted Logon, a replayed application message with PossDup, and acknowledgements for every report. Coordinate the test endpoint’s resend case before running it. Reconnecting alone does not pass. The script exits with a failure when a case fails.
-- sends the run to POST /api/ with the API key in DALP_API_KEY. The caller needs the role that edits obligations. The record keeps the environment, the endpoint, each case and the overall outcome with the obligation version, so a version's certification stays with it. A Reporting reader lists the runs with GET /api/.
What is anchored and what is not
FIX trade reports are not anchored on chain. The evidence of what was sent is the session's message log and its sent log, which keep the exact bytes of each message. A trade journal filing is anchored like any other filing when its obligation has chain anchoring turned on, as How an anchor proves a filing was not altered describes. Contract notes and trade confirmations are a record for the holder, covered in Investor statements.
Watch the queue
The Filings screen shows a “Transaction reports to the regulator” panel to any Reporting reader once the organisation has a published version that configures a session, a session that has connected before, a queued report or an unmatched answer:
- “Regulator connection” reads “Connected” with “Logged on” and the UTC time, “Disconnected” with “Reports wait until the connection returns”, or “Not set up”. The platform keeps one session record for each pair of sender and target CompIDs the organisation has used, so an organisation whose CompIDs changed shows one cell for each pair, named by the target CompID.
- “Waiting to be sent” counts the queued reports, with “Oldest waiting” in minutes.
- “Unmatched regulator answers” lists the newest 50 exceptions with the time received, the report each named and the reason.
A session reads as connected while one platform instance runs it and the regulator has accepted its Logon. The same state is available from GET /api/, which returns whether a published version configures a session, each session's state and Logon time, the queue count, the age of the oldest queued report, and the newest 50 exceptions. A report's age counts from when it was first queued, so a report queued again after a correction keeps its original age.
Across organisations, the platform publishes the queue size, the age of the oldest queued report, how many sessions are logged on, how many are held, and how many messages were sent, resent and rejected at session level. The metrics carry no organisation label. The alert Transaction fires when the oldest queued report has waited past the deployment's window, which defaults to 15 minutes and is set with the observability chart value dalp.. The alert Transaction fires when a break of the recent trading days stays open for 15 minutes, using the dalp. gauge, and its runbook entry is in the observability troubleshooting guide.
Related pages
Read Reporting obligation for how a version freezes its configuration, Obligations for the duty that carries the trade journal, and Audit trail to find who corrected or recorded a report.