Skip to content

Message Level Status

Philip Helger edited this page Jul 24, 2026 · 6 revisions

Message Level Status (MLS)

Overview

MLS is a Peppol-defined asynchronous feedback mechanism that allows the receiving Access Point (C3) to inform the sending Access Point (C2) about the processing outcome of a received business document. MLS messages are themselves regular Peppol documents sent via AS4, reusing the existing outbound transaction infrastructure including retry logic.

The AP handles MLS in both roles:

  • As C3 (receiving): After determining the forwarding outcome, the AP generates and sends an MLS response back to C2.
  • As C2 (sending): The AP receives MLS responses asynchronously via AS4, correlates them with outbound transactions, and updates the MLS status.

MLS Response Codes

Code Name When sent
AP Approved Successful delivery with confirmation (forwarding mode http_post_sync or http_post_async)
AB Accepted Blind Delivered without delivery confirmation (forwarding mode sftp, s3_link, or filesystem)
RE Rejection Document failed verification (status reason SV, BV and/or BW — see verifier-driven issue mapping) or permanent forwarding failure to C4 (status reason FD)

The choice between AP and AB depends on the forwarding mode: HTTP modes provide delivery confirmation, while SFTP, S3, and Filesystem do not.

MLS Status Reason Codes

Included in MLS rejection (RE) response line items.

Code Name Description
BV Business Rule Violation (Fatal) Fatal business rule violation -- document does not conform to required business rules
BW Business Rule Warning Non-fatal issue, may accompany a BV but cannot trigger rejection alone
FD Failure of Delivery Permanent inability to forward the document to C4 (error field is NA)
SV Syntax Violation XML schema or syntax validation failure

MLS Sending (AP as C3)

When the AP receives an inbound business document and determines the forwarding outcome, it generates an MLS response and sends it back to C2.

Triggers for MLS creation

Trigger MLS response code Status reason
Successful forwarding via HTTP AP (Approved) --
Successful forwarding via SFTP, S3, or Filesystem AB (Accepted Blind) --
Document verification failure RE (Rejection) SV, BV and/or BW per issue — chosen by the verifier (see below)
Permanent forwarding failure (max retries exhausted) RE (Rejection) FD (Failure of Delivery)

MLS sending strategy (MLS_TYPE)

The mls.type configuration property controls when MLS responses are sent:

Value Behavior
ALWAYS_SEND MLS response is sent for all outcomes (RE, AP, or AB)
FAILURE_ONLY (default) MLS response is sent only on rejection (RE)

The active strategy is captured per inbound transaction at reception time, so changing the configuration only affects newly received documents.

MLS sending (as C3) can be globally disabled via mls.sending.enabled=false -- see Configuration.

MLS_TO routing

By default, the MLS response is sent to C2's Peppol Seat ID. The Sender Backend can override this per outbound transaction by providing the mlsTo query parameter on the submission API, which places an alternative Peppol participant ID in the SBDH extension. This is useful when C2 operates multiple access points.

The mlsTo value can be set on:

  • POST /api/outbound/submit/{senderID}/{receiverID}/{docTypeID}/{processID}/{c1CountryCode} -- via the mlsTo query parameter (see API Specification)
  • POST /api/outbound/submit-auto/{senderID}/{receiverID}/{c1CountryCode} -- via the mlsTo query parameter (see API Specification)
  • POST /api/outbound/submit-s3 -- via the mlsTo field in the JSON body (see API Specification)

Determining the effective MLS receiver (since v0.11.0)

When acting as C3, the AP determines where to send the MLS response following the Peppol MLS SPOG section 5.1 fallback chain:

  1. Use the MLS_TO value from the incoming SBDH only if it is present, syntactically a valid 0242 SPIS participant ID, and its 6-digit Main ID correlates to the sending C2's SPID Main ID (derived from the C2 signing certificate's CN). Redirecting an MLS to a different Service Provider is not allowed, so a non-correlating MLS_TO is treated as invalid and ignored.
  2. Otherwise fall back to the default SPID receiver 0242:<C2 Main ID>, derived from the C2 Seat ID.

The validation happens at reception time in InboundOrchestrator.getValidMlsTo(...): a valid, correlating MLS_TO is persisted on the inbound transaction (mls_to), while an absent, syntactically invalid, or non-correlating value is dropped to NULL (with a warning logged) so the default SPID is used.

SMP lookup fallback (since v0.11.0)

Before the MLS is sent, the effective MLS receiver is resolved via an SMP lookup (OutboundOrchestrator). Per MLS SPOG section 5.4, an unreachable receiver is handled differently for MLS than for regular business documents:

  • Custom MLS_TO not reachable — if the effective receiver is a custom MLS_TO (i.e. it differs from the default SPID) and it cannot be resolved in SMP (participant or MLS service not registered), the AP fires the onSpecialMlsToNotReachable() notification and retries the lookup against the default SPID receiver. Because a valid MLS_TO always shares C2's Main ID, the default SPID is 0242:<Main ID> and is derivable from the receiver itself.
  • Default SPID not reachable — if the default SPID receiver itself cannot be resolved, the MLS transaction is queued and retried (per PNP Rule MLS-4) instead of being marked as permanently failed. Retries (which go through the generic two-argument sending path) reconstruct the same fallback automatically for mls_response transactions, so the behaviour is identical to the first attempt.

In contrast, for a regular business_document transaction an unresolvable receiver remains a permanent failure. Transient SMP errors (retry-feasible errors, circuit-breaker rejection) always queue a retry against the same receiver, independent of MLS.

Sending flow

sequenceDiagram
    participant AP as AP (C3)
    participant DB as DB
    participant P4 as phase4
    participant RA as Remote AP (C2)

    AP->>AP: Determine MLS response code (RE, AP, or AB)
    AP->>AP: Check MLS_TYPE (ALWAYS_SEND or FAILURE_ONLY)
    note over AP: If FAILURE_ONLY and outcome is success: skip MLS

    AP->>DB: Create outbound_transaction (transaction_type = mls_response)
    AP->>DB: Update inbound_transaction (mls_response_code, mls_outbound_transaction_id)

    AP->>P4: Send MLS via AS4
    P4->>RA: AS4 UserMessage (MLS)
    RA-->>P4: AS4 Receipt
    P4-->>AP: AS4 result

    AP->>DB: Insert outbound_sending_attempt
    AP->>DB: Update outbound_transaction status
Loading

The MLS sending is recorded as an outbound_transaction with transaction_type = mls_response, linked to the original inbound transaction via mls_inbound_transaction_id. The inbound transaction's mls_outbound_transaction_id is set to reference it back.

MLS response transactions follow the same retry logic as business document transactions -- see Retry and Resilience Patterns.

Verifier-driven issue mapping (since v0.10.0)

Breaking change in v0.10.0. The SPI signature changed: IInboundDocumentVerifierSPI.verifyInboundDocument now returns @Nullable MlsOutcome instead of ESuccess. A null return (or any non-REJECTION outcome) means the verifier passes; a non-null MlsOutcome with response code REJECTION causes rejection and the contained MlsOutcomeIssue entries flow into the MLS line responses. See News and noteworthy for the migration note.

Rejection MLS responses now contain a line response per failed validation entry instead of a single generic BV/NA placeholder. Issues are grouped by errorField into MLS line responses (one line response per distinct error field).

Verifier implementations decide the status reason of each issue. The bundled PhormDocumentVerifier in phoss-ap-validation maps phive ValidationResultLists to MlsOutcome via PhiveToMlsMapper:

phive source phive severity MLS status reason
XSD validation artefact (EValidationBaseType.XSD) any failure SV (Syntax Violation)
Schematron / other artefact ERROR or FATAL_ERROR BV (Business Rule Violation, fatal)
Schematron / other artefact WARN BW (Business Rule Warning)
any below WARN dropped

Each issue's errorField and description are derived from the phive IError:

MLS field Derived from
errorField IError.getErrorFieldName() (the failed Schematron assertion's XPath context) if present; else the formatted IError.getErrorLocation(); else NA (CPeppolMLS.LINE_ID_NOT_AVAILABLE)
description [ruleID] + localised IError.getErrorText(locale) when both are present; otherwise whichever is available; falls back to "Validation failure"

If the call to the configured Phorm Validation Service itself fails (network error, malformed response, missing token), the verifier produces a single BV/NA rejection with description "Phorm validation service call failed - see server log for details". FD is reserved for permanent inability to forward to C4 and is not used for verifier-call failures.

When the Phorm URL is not configured (verification.phorm.url empty), the verifier returns null — the inbound document passes the verification step.

MLS Reception (AP as C2)

When the AP sends a business document to a remote C3, it expects an MLS response back. The MLS response arrives asynchronously as a separate AS4 message.

Reception flow

sequenceDiagram
    participant RA as Remote AP (C3)
    participant PS as phase4 Servlet
    participant DB as DB

    RA->>PS: AS4 UserMessage (MLS response)
    PS->>PS: Parse MLS, extract SBDH Instance ID reference
    PS->>DB: Lookup outbound_transaction by SBDH Instance ID
    DB-->>PS: original transaction

    PS->>DB: Update outbound_transaction MLS fields
    PS-->>RA: AS4 Receipt
Loading

Correlation

The AP correlates the incoming MLS response with the original outbound business document transaction using the SBDH Instance Identifier referenced in the MLS message. On successful correlation, the outbound transaction is updated with:

Field Value
mls_status received_ap, received_ab, or received_re (mapped from the MLS response code)
mls_received_dt The AS4 timestamp when the MLS response was received
mls_id The MLS document's own ID (cbc:ID)
mls_inbound_transaction_id FK to the inbound transaction representing the received MLS message

If no matching outbound transaction is found, the AP logs a warning and triggers the onInboundMLSCorrelationError() notification.

MLS reception status

Tracked on outbound business document transactions as mls_status:

Value Description
pending MLS response has not yet been received
received_ap MLS response received with code AP (Approved)
received_ab MLS response received with code AB (Accepted Blind)
received_re MLS response received with code RE (Rejection)
not_applicable MLS is not expected for this transaction (e.g., MLS response transactions themselves)

SLR Compliance (MLS Timing Requirements)

The Peppol Network Policy defines Service Level Requirements (SLRs) for MLS timing, measured monthly for payloads smaller than 10 MB.

MLS milestones

All milestones are based on AS4 MessageInfo/Timestamp values (UTC).

Milestone Definition Where captured in phoss-ap
M1 AS4 Timestamp when the business document transmission is initiated at C2 outbound_sending_attempt.as4_timestamp (outbound) or inbound_transaction.as4_timestamp (inbound)
M2 AS4 Timestamp when the MLS message transmission is initiated at C3 outbound_sending_attempt.as4_timestamp on the MLS response's successful sending attempt
M3 Date/time when the MLS message is received at C2 outbound_transaction.mls_received_dt

SLR thresholds

SLR Constraint Target Measurement
SLR MLS-1 (Latest MLS Sending) M2 - M1 <= 20 minutes 99.5% This AP as C3: time from receiving a business document to successfully sending the MLS response
SLR MLS-2 (Latest MLS Reception) M3 - M1 <= 25 minutes 99.5% This AP as C2: time from successfully sending a business document to receiving the MLS response

Monitoring SLR compliance

The AP provides dedicated API endpoints for monitoring SLR compliance:

  • GET /api/mls/sla/mls1 -- MLS-1 SLA report (this AP as C3, measuring M2 - M1). See API Specification.
  • GET /api/mls/sla/mls2 -- MLS-2 SLA report (this AP as C2, measuring M3 - M1). See API Specification.

Both endpoints return:

Field Description
totalCount Total number of MLS responses measured
withinSlaCount Number within the threshold
compliancePercent Actual compliance percentage
targetPercent Required target (99.5)
thresholdSeconds SLA threshold in seconds (1200 for MLS-1, 1500 for MLS-2)
meetingSla Whether the target is met
entries Individual measurements with sbdhInstanceID, m1, m2OrM3, durationSeconds, withinSla

API Endpoints

All MLS-related API endpoints. Full details in API Specification.

Method Path Description
GET /api/mls/missing List inbound transactions that have not yet received an MLS response
GET /api/mls/sla/mls1 MLS-1 SLR compliance report (C3 side: M2 - M1)
GET /api/mls/sla/mls2 MLS-2 SLR compliance report (C2 side: M3 - M1)

MLS-related fields are also returned on existing transaction status endpoints:

Method Path MLS fields
GET /api/outbound/status/{sbdhInstanceID} mlsTo, mlsStatus, mlsReceivedDT, mlsId
GET /api/inbound/status/{sbdhInstanceID} mlsTo, mlsType, mlsResponseCode, mlsOutboundTransactionID

Database Fields

On inbound_transaction (MLS sending as C3)

Field Description
mls_to MLS_TO target participant ID (from SBDH extension)
mls_type MLS sending strategy at time of reception (FAILURE_ONLY or ALWAYS_SEND)
mls_response_code MLS response code sent or to be sent (RE, AP, AB)
mls_outbound_transaction_id FK to the outbound_transaction representing the MLS sending

On outbound_transaction (MLS reception as C2)

Field Description
mls_to MLS_TO override participant ID (only for business_document transactions)
mls_status MLS reception status: pending, received_ap, received_ab, received_re, not_applicable
mls_received_dt When the MLS response was received
mls_id The MLS message's own ID (cbc:ID)
mls_inbound_transaction_id FK to the inbound_transaction that represents the received MLS message (only for mls_response transactions)

On outbound_sending_attempt (MLS milestone timestamps)

Field Description
as4_timestamp AS4 MessageInfo/Timestamp (UTC). For business_document attempts this is MLS Milestone M1; for mls_response attempts this is MLS Milestone M2.

See Database Design Notes for the full schema.

Configuration

All MLS configuration properties. See Configuration Properties for the full configuration reference.

Property Default Description
mls.sending.enabled true Global switch to enable or disable all outbound MLS sending. When false, no MLS responses are sent for any inbound transaction.
mls.type ALWAYS_SEND MLS sending strategy: FAILURE_ONLY (only on rejection) or ALWAYS_SEND (for all outcomes). Captured per inbound transaction at reception time.

Notifications and lifecycle events

The AP dispatches MLS-related events to two runtime extension SPIs, so integrations can react the moment an MLS is received instead of polling the MLS query API.

Success side — IAPLifecycleEventSPI:

Event Method When
MLS received & correlated onInboundMLSCorrelated() An inbound MLS response was successfully matched to the outbound business document it refers to

The onInboundMLSCorrelated() callback carries:

Parameter Type Description
sMlsTransactionID String Transaction ID of the inbound MLS
sReferencedSbdhInstanceID String SBDH Instance Identifier of the original outbound document
sCorrelatedOutboundTransactionID String Transaction ID of the correlated outbound business document — use it to look up sender, receiver, document type and process
eMlsResponseCode EPeppolMLSResponseCode Response code carried by the MLS (ACCEPTANCE / ACKNOWLEDGING / REJECTION)
eMlsReceptionStatus EMlsReceptionStatus Resulting reception status persisted on the outbound transaction (RECEIVED_AP / RECEIVED_AB / RECEIVED_RE)
sMlsDocumentID String (nullable) Document ID of the received MLS ApplicationResponse
aMlsReceivedDT OffsetDateTime AS4 reception timestamp of the MLS
aRoundTrip Duration (nullable) Wall-clock time from outbound send completion to MLS reception; powers the MLS-1 / MLS-2 SLA histograms

Failure side — IAPNotificationHandlerSPI:

Event Method When
MLS correlation error onInboundMLSCorrelationError() An inbound MLS response cannot be correlated to any outbound business document (no matching SBDH Instance ID found)
Special MLS_TO not reachable (since v0.11.0) onSpecialMlsToNotReachable() An outbound MLS could not be delivered to a custom MLS_TO receiver because it was not resolvable via SMP, and sending falls back to the default SPID receiver (see SMP lookup fallback)

The onSpecialMlsToNotReachable() callback carries:

Parameter Type Description
sOutboundTransactionID String Transaction ID of the outbound MLS being sent
sReferencedSbdhInstanceID String SBDH Instance Identifier of the original business document the MLS refers to
sAttemptedMlsToParticipantID String The custom MLS_TO receiver participant ID that could not be reached
sFallbackDefaultSpidParticipantID String The default SPID receiver participant ID used as the fallback target

The OpenTelemetry metrics handler additionally increments the phoss.ap.outbound.mls.special_to_not_reachable counter.

Clone this wiki locally