`paymentResponse.paymentRail` — `AANI`, `FTS`, or `LFI` for an on-us transfer — is required on every `PATCH /payment-log/{id}` and selects one of three per-rail request bodies. Each rail has its own status set, its own reject reason code namespace, and its own rule on `paymentTransactionId`. `GET /payment-log` reads the payment back in the same shape.
What changed
The payment log records what the API Hub knows about a payment, but not how it was settled. paymentResponse.paymentTransactionId already depends on the rail to be interpreted — its v2.1 description singles out AANI-generated identifiers in prose — while nothing in the record states which rail produced it. The result is one request body covering three materially different settlement processes, with the differences left to prose.
v2.2 makes the rail explicit and then uses it. paymentResponse.paymentRail is a closed enum of AANI (the UAE instant payment platform), FTS (UAEFTS, the CBUAE funds transfer system), and LFI (settled internally, both accounts held at the same LFI, so the payment never reached an external rail). It is **required on every patch**, and it is the OpenAPI discriminator: CbuaePatchPaymentRecordBody becomes a oneOf over CbuaePatchPaymentRecordBodyAani, CbuaePatchPaymentRecordBodyFts and CbuaePatchPaymentRecordBodyLfi, selected on paymentResponse.paymentRail. Each member is additionalProperties: false, so a field that does not belong to the named rail is rejected rather than ignored.
**The statuses a payment may move to now depend on the rail, and two rails have a non-terminal success.**
On AANI the statuses are AcceptedSettlementCompleted, AcceptedWithoutPosting and Rejected. AcceptedSettlementCompleted is **not terminal**: it records the payment having been passed to AANI. Once AANI confirms it, the LFI MUST patch the record again, to AcceptedWithoutPosting.
On FTS the statuses are AcceptedSettlementCompleted, AcceptedCreditSettlementCompleted and Rejected. AcceptedSettlementCompleted is again **not terminal** — it records the hand-off to UAEFTS — and the LFI MUST patch again to AcceptedCreditSettlementCompleted once UAEFTS confirms.
On LFI the statuses are AcceptedCreditSettlementCompleted and Rejected, and both are terminal. Settlement is internal to the LFI, so there is no hand-off to an external rail to report as an intermediate step.
Two things are withdrawn from the operation on every rail. Pending is no longer a patchable status: a payment is Pending precisely because the LFI has not yet routed it, so there is no rail to name. And paymentResponse.OpenFinanceBilling is no longer patchable through this operation on any rail.
**Reject reason codes are namespaced to the rail.** In v2.1 a single pattern, ^(AANI|FTS|LFI)\.[A-Za-z0-9]+$, admits any namespace on any payment. In v2.2 each rail member references its own schema — CbuaePaymentLogRejectReasonCodeAani, …Fts, …Lfi — so an FTS.* code sent against a payment routed to AANI no longer validates. A payment rejected at the LFI before it reached AANI or UAEFTS is still reported against the rail it was routed to, and carries that rail's reason codes.
**paymentTransactionId follows the rail that issues it.** It is present on AANI and FTS, carrying the identifier that rail issued to the Originating LFI, and the LFI MUST populate it once the rail has issued it — that is, alongside either of that rail's success statuses. It is absent from the LFI member entirely, since nothing outside the LFI issues an identifier for a payment booked on its own ledger. It is omitted where the payment was rejected before the rail issued one. It is not the same value as transactionId in the Bank Data Sharing API.
**GET /payment-log reads the payment back in the shape it was patched in.** The previously inline paymentResponse object becomes CbuaePaymentLogPaymentResponse, a oneOf with one member per rail plus CbuaePaymentLogPaymentResponseUnrouted — a payment the LFI has not routed, which carries no paymentRail and is Pending or Rejected. The unrouted member is the only one that still accepts a reject code from any of the three namespaces, since a payment rejected before routing has no rail to scope it to. paymentTransactionId is also now returned on the AANI and FTS members, having been absent from this operation's response since v2.1 even though the patch operation set it.
The rail records how the payment was **executed**, not how it was requested: where an LFI's routing rules settle an instant payment on another rail, the field reports the rail actually used. A payment settles over exactly one rail — this holds for file payments as well as single payments, so one value describes the whole record.
This is a breaking change and it requires action from every LFI. An LFI built against v2.1 that patches a payment without naming a rail is rejected under v2.2, as is one that patches Pending, sends OpenFinanceBilling, or sends a reject code from the wrong namespace. LFIs settling over AANI or UAEFTS must also emit the second patch that carries the payment to its terminal status, which v2.1 did not require. The API Hub enforcement date is set separately from the version cutover.
The change is published in the v2.2.2 release of the Consent Manager OpenAPI document in the api-specs repository, which is what the endpoint pages below render.