- Copyright and Liability Disclaimer
- Version History
- Paylink Resolution
  - post Resolve a PrP Paylink
- Payment Request
  - post Request a payment
  - post Conclude payment
- Loyalty Request
  - post Request payer loyalty info
- Identity Request
  - post Request payer identity info
- Session Extension Request
  - post Request a session extension
- Flow Conclusion
  - post Conclude flow
- Implementation Requirements
- Glossary

API docs by Redocly

# QR+ Payer Service Provider API (1.2.0)

Download OpenAPI specification:

This API definition captures the Payer Service Provider API specification. The PrSP hosts this API for orchestration of enrichment flows.

This API does not cover the interfaces between the Payer Service Provider and their Payers. The API assumes that the following exchanges have been successfully completed before ths API is used:

- Paylink generation: A new Paylink was generated by a Paylink Provider at the request of a Payer or Payee.
- Paylink presentment: The Paylink was presented for scanning.
- Paylink scanning: The Paylink was scanned and presented to a QR+ Service Provider for processing.

To contextualize how QR+ fits into a complete payment, the sequence diagram below also includes placeholder steps for processes that fall outside the QR+ API surface. These placeholders are not specified by QR+ and will vary by payment rail and participant — they are shown only to make the integration points with rail-specific infrastructure explicit.

The non-QR+ placeholders are:

- Rail Selection — the PrSP and Payer decide which of the acceptance options presented by the PeSP will be used for this payment.
- Prior to Approval (rail-specific) — rail infrastructure may be used to gather additional information used during the Payer's approval of the payment.
- Push Payment Initiation (rail-specific).
- Pull Payment Initiation (rail-specific).

In the diagram, treat these placeholders as black boxes: their internal exchanges are out of scope for this specification, but their entry/exit points anchor where QR+ flows hand off to and resume from the underlying rail.

A high-level sequence diagram of the subsequent exchange:

## Copyright and Liability Disclaimer

Full Copyright and Liability Disclaimer

The QR+ Standard has been developed by the South African Reserve Bank for use by participants in the National Payment System. All intellectual property rights, including copyright and any related rights in and to the QR+ Standard, are owned by, or vest in, the South African Reserve Bank. No participant or other person acquires any ownership right, title or interest in the QR+ Standard by virtue of its access to, implementation of or use of the QR+ Standard. The QR+ Standard may not be copied, reproduced, adapted, amended, transferred, ceded, assigned, sublicensed, published, distributed or otherwise made available to any third party, in whole or in part, without the prior written consent of the South African Reserve Bank, except to the extent expressly permitted by the South African Reserve Bank.

Participants are responsible for developing, implementing, maintaining and operating their own systems, processes and controls to ensure compliance with the QR+ Standard and with all applicable laws, regulatory requirements and industry standards. The South African Reserve Bank does not warrant that the QR+ Standard, or any implementation thereof by a participant, will be error-free, uninterrupted, fit for any particular purpose or suitable for any participant’s specific operational or commercial requirements.

To the fullest extent permitted by law, the South African Reserve Bank shall not be liable for any loss, damage, claim, cost or expense, whether direct, indirect, special, incidental or consequential, arising from or in connection with a participant’s access to, reliance on, implementation of, or use of the QR+ Standard, including any failure by a participant to comply with the QR+ Standard or any applicable legal or regulatory requirement.

## Version History

Version: 1.2.0 (2026-06-05)

Release candidate 1.2.0-rc3 approved for publication

## Paylink Resolution

Use the 'resolve' endpoint as the starting point of a flow when your customer has scanned a PrP Paylink. Provide the details of a scanned Paylink as well as the Payee identity information. Use the tracking object returned with a successful response to execute payment-, loyalty- or identity- requests.

## Resolve a PrP Paylink

Use this endpoint to resolve a Payer-presented Paylink (PrP) and initiate a flow. This is called by the Payee Service Provider when a Payee receives and submits a PrP paylink from a Payer.

##### Authorizations:

bearerAuth

##### Request Body schema: application/json
required

required | object (NewPeSpTracker) A new PeSP message tracking object submitted by the PeSP when initiating a new flow
required | any
required | any Payee details to be displayed to the Payer when getting informed approval

### Responses

http://localhost:3000/resolve-paylink

### Request samples

- Payload

Content type
application/json
`
{

- "tracking":
  {
  - "payeeSpFlowId": "823b24e1-cccc-4276-b80b-37b3e14d05ae",
  - "payeeSpMessageSeq": 1
  },
- "paylink":
  {
  - "kind": "URI_ENCODED",
  - "anchorDomain": "pay.gov.za",
  - "flowTypeIndicator": "general",
  - "spIndicator": "xmpl",
  - "paylinkValue": "823hf92hfg9nv9adsn98284",
  - "presentmentFormat": "QR"
  },
- "payeeIdentification":
  {
  - "kind": "S_BUSINESS_BASIC",
  - "tradingName": "V&A Waterfront Coffee",
  - "legalName": "ABC Holdings Pty Ltd",
  - "siteName": "V&A Waterfront"
  }

}`

### Response samples

- 200
- 422
- default

Content type
application/json
`
{

- "tracking":
  {
  - "payerSpFlowId": "823b24e1-cccc-4276-b80b-37b3e14d05ae",
  - "payerSpMessageSeq": 1,
  - "payeeSpFlowId": "823b24e1-cccc-4276-b80b-37b3e14d05ae",
  - "payeeSpMessageSeq": 1,
  - "sessionInfo":
    {
    - "status": "OPEN",
    - "asAt": "2023-10-05T14:48:00.000Z",
    - "expiresAt": "2023-10-05T14:48:00.000Z",
    - "createdAt": "2023-10-05T14:48:00.000Z"
    }
  }

}`

## Payment Request

Use the Payment Request endpoints to effect a payment to the Payee. Initiate the process by submitting the following information as part of the Payment Request:

- An active tracking object
- The list of payee payment acceptance options
- The transaction details

The PrSP will reply to this request through a callback to your PeSP API.

If the Payer application is connected, the PrSP will reach out to the Payer on their mobile device to establish transaction approval and if needed, source a funding account decision. If the Payer chooses to use a push payment method, the PrSP proceeds to initiate such payment and shares the outcome with you through a CONFIRMATION reply. If the Payer chooses to use a pull payment method, the PrSP delegates the payment to your service by providing the necessary payment credentials and authorization using a DELEGATION reply.

Use the payment conclusion endpoint to communicate final acceptance of the payment and trigger the PrSP to send a payment notification to the Payer.

## Request a payment

Use this endpoint to request a payment from the Payer Service Provider during an open flow. The PrSP will reply via callback to the Payee Provider API's /reply-payment endpoint.

##### Authorizations:

bearerAuth

##### Request Body schema: application/json
required

required | object (ActivePeSpFlowTracker) An active Payee service provider tracking object exchanged during a flow
required | object The set of acceptance options the PeSP offers for payment.
required | any Transaction details for the payment request

### Responses

### Callbacks

http://localhost:3000/request-payment

### Request samples

- Payload

Content type
application/json
`
{

- "tracking":
  {
  - "payerSpFlowId": "823b24e1-cccc-4276-b80b-37b3e14d05ae",
  - "payerSpMessageSeq": 1,
  - "payeeSpFlowId": "823b24e1-cccc-4276-b80b-37b3e14d05ae",
  - "payeeSpMessageSeq": 1
  },
- "acceptanceOptions": { },
- "transactionDetails":
  {
  - "kind": "S_FIXED",
  - "currency": "ZAR",
  - "amount": "1234.56"
  }

}`

### Response samples

- 202
- 422
- default

Content type
application/json
`
{

- "status": "Accepted"

}`

### Callback payload samples

Callback
POST: /reply-payment
Content type
application/json
Example
CONFIRM
`
{

- "kind": "CONFIRM",
- "tracking":
  {
  - "payerSpFlowId": "823b24e1-cccc-4276-b80b-37b3e14d05ae",
  - "payerSpMessageSeq": 1,
  - "payeeSpFlowId": "823b24e1-cccc-4276-b80b-37b3e14d05ae",
  - "payeeSpMessageSeq": 1,
  - "sessionInfo":
    {
    - "status": "OPEN",
    - "asAt": "2023-10-05T14:48:00.000Z",
    - "expiresAt": "2023-10-05T14:48:00.000Z",
    - "createdAt": "2023-10-05T14:48:00.000Z"
    }
  },
- "confirmation":
  {
  - "kind": "S_PAY_SHAP_PUSH",
  - "instrument":
    {
    - "kind": "PUSH_PBPX_MBNO",
    - "proxy": "string"
    },
  - "paymentReference": "INV-1234",
  - "messageIdentification": "string",
  - "currency": "ZAR",
  - "amount": "1234.56"
  }

}`

## Conclude payment

Use this endpoint to conclude a payment by acknowledging a CONFIRMATION or confirming a DELEGATION.

##### Authorizations:

bearerAuth

##### Request Body schema: application/json
required

required | object (ActivePeSpFlowTracker) An active Payee service provider tracking object exchanged during a flow
required | any

### Responses

http://localhost:3000/conclude-payment

### Request samples

- Payload

Content type
application/json
`
{

- "tracking":
  {
  - "payerSpFlowId": "823b24e1-cccc-4276-b80b-37b3e14d05ae",
  - "payerSpMessageSeq": 1,
  - "payeeSpFlowId": "823b24e1-cccc-4276-b80b-37b3e14d05ae",
  - "payeeSpMessageSeq": 1
  },
- "conclusion":
  {
  - "status": "SUCCESS",
  - "currency": "ZAR",
  - "amount": "1234.56",
  - "details":
    {
    - "kind": "S_PAY_SHAP_RTP",
    - "originalEndToEndIdentification": "string",
    - "originalUetr": "string"
    }
  }

}`

### Response samples

- 200
- 422
- default

Content type
application/json
`
{

- "status": "ACKNOWLEDGED",
- "tracking":
  {
  - "payerSpFlowId": "823b24e1-cccc-4276-b80b-37b3e14d05ae",
  - "payerSpMessageSeq": 1,
  - "payeeSpFlowId": "823b24e1-cccc-4276-b80b-37b3e14d05ae",
  - "payeeSpMessageSeq": 1,
  - "sessionInfo":
    {
    - "status": "OPEN",
    - "asAt": "2023-10-05T14:48:00.000Z",
    - "expiresAt": "2023-10-05T14:48:00.000Z",
    - "createdAt": "2023-10-05T14:48:00.000Z"
    }
  }

}`

## Loyalty Request

Use the Loyalty Request endpoints to gain access to information pertaining to the Payer's membership to a Payee loyalty program. The Loyalty Request is executed on behalf of the Payee by providing loyalty program information in addition to an active tracking object. The Payer Service Provider might reach out to the Payer for approval to share this information if it does not have an established mandate from the Payer.

The PrSP will reply to this request though a callback to the PeSP API.

Enrolment to the Loyalty program is beyond the scope of this interface.

## Request payer loyalty info

Use this endpoint to request loyalty program membership information from the Payer Service Provider during an open flow. The PrSP will reply via callback to the Payee Provider API's /reply-loyalty endpoint.

##### Authorizations:

bearerAuth

##### Request Body schema: application/json
required

required | object (ActivePeSpFlowTracker) An active Payee service provider tracking object exchanged during a flow
loyaltyProgramName required | string (LoyaltyProgramName) [ 1 .. 128 ] characters The name of the loyalty program. TODO: namespace discussions

### Responses

### Callbacks

http://localhost:3000/request-loyalty

### Request samples

- Payload

Content type
application/json
`
{

- "tracking":
  {
  - "payerSpFlowId": "823b24e1-cccc-4276-b80b-37b3e14d05ae",
  - "payerSpMessageSeq": 1,
  - "payeeSpFlowId": "823b24e1-cccc-4276-b80b-37b3e14d05ae",
  - "payeeSpMessageSeq": 1
  },
- "loyaltyProgramName": "BigBucks"

}`

### Response samples

- 202
- 422
- default

Content type
application/json
`
{

- "status": "Accepted"

}`

### Callback payload samples

Callback
POST: /reply-loyalty
Content type
application/json
Example
APPROVE
`
{

- "kind": "APPROVE",
- "tracking":
  {
  - "payerSpFlowId": "823b24e1-cccc-4276-b80b-37b3e14d05ae",
  - "payerSpMessageSeq": 1,
  - "payeeSpFlowId": "823b24e1-cccc-4276-b80b-37b3e14d05ae",
  - "payeeSpMessageSeq": 1,
  - "sessionInfo":
    {
    - "status": "OPEN",
    - "asAt": "2023-10-05T14:48:00.000Z",
    - "expiresAt": "2023-10-05T14:48:00.000Z",
    - "createdAt": "2023-10-05T14:48:00.000Z"
    }
  },
- "membershipReference": "PHE-184638"

}`

## Identity Request

Use the Identity Request endpoints to gain access to PrSP attestations as to the identity of the Payer. The Identity Request is done on behalf of the Payee by defining the type of attestation required and including an active tracking object. The Payer Service Provider will respond with identity information if approval to such sharing can be gathered from the Payer. A reply to this request will be done through a callback to your PeSP API.

## Request payer identity info

Use this endpoint to request identity information from the Payer Service Provider during an open flow. The PrSP will reply via callback to the Payee Provider API's /reply-identity endpoint.

##### Authorizations:

bearerAuth

##### Request Body schema: application/json
required

required | object (ActivePeSpFlowTracker) An active Payee service provider tracking object exchanged during a flow
required | Array of "S_DFID" (string) or "S_NAME_AND_DATE_OF_BIRTH" (string) [ 1 .. 5 ] items

### Responses

### Callbacks

http://localhost:3000/request-identity

### Request samples

- Payload

Content type
application/json
`
{

- "tracking":
  {
  - "payerSpFlowId": "823b24e1-cccc-4276-b80b-37b3e14d05ae",
  - "payerSpMessageSeq": 1,
  - "payeeSpFlowId": "823b24e1-cccc-4276-b80b-37b3e14d05ae",
  - "payeeSpMessageSeq": 1
  },
- "identificationOptions":
  [
  - "S_DFID"
  ]

}`

### Response samples

- 202
- 422
- default

Content type
application/json
`
{

- "status": "Accepted"

}`

### Callback payload samples

Callback
POST: /reply-identity
Content type
application/json
Example
APPROVE
`
{

- "kind": "APPROVE",
- "tracking":
  {
  - "payerSpFlowId": "823b24e1-cccc-4276-b80b-37b3e14d05ae",
  - "payerSpMessageSeq": 1,
  - "payeeSpFlowId": "823b24e1-cccc-4276-b80b-37b3e14d05ae",
  - "payeeSpMessageSeq": 1,
  - "sessionInfo":
    {
    - "status": "OPEN",
    - "asAt": "2023-10-05T14:48:00.000Z",
    - "expiresAt": "2023-10-05T14:48:00.000Z",
    - "createdAt": "2023-10-05T14:48:00.000Z"
    }
  },
- "identification":
  {
  - "kind": "S_DFID",
  - "data": "string"
  }

}`

## Session Extension Request

Use the Session Extension Request endpoints to request the PrSP to move out the current session expiry deadline to allow more time for the PeSP to initiate new Requests.

## Request a session extension

Use this endpoint to request that the PrSP extend the session lifespan by pushing out the session expiration deadline. The PrSP will return an updated session expiration timestamp in the tracking object.

##### Authorizations:

bearerAuth

##### Request Body schema: application/json
required

required | object (ActivePeSpFlowTracker) An active Payee service provider tracking object exchanged during a flow

### Responses

http://localhost:3000/request-session-extension

### Request samples

- Payload

Content type
application/json
`
{

- "tracking":
  {
  - "payerSpFlowId": "823b24e1-cccc-4276-b80b-37b3e14d05ae",
  - "payerSpMessageSeq": 1,
  - "payeeSpFlowId": "823b24e1-cccc-4276-b80b-37b3e14d05ae",
  - "payeeSpMessageSeq": 1
  }

}`

### Response samples

- 200
- 422
- default

Content type
application/json
`
{

- "tracking":
  {
  - "payerSpFlowId": "823b24e1-cccc-4276-b80b-37b3e14d05ae",
  - "payerSpMessageSeq": 1,
  - "payeeSpFlowId": "823b24e1-cccc-4276-b80b-37b3e14d05ae",
  - "payeeSpMessageSeq": 1,
  - "sessionInfo":
    {
    - "status": "OPEN",
    - "asAt": "2023-10-05T14:48:00.000Z",
    - "expiresAt": "2023-10-05T14:48:00.000Z",
    - "createdAt": "2023-10-05T14:48:00.000Z"
    }
  }

}`

## Flow Conclusion

Use the flow conclusion endpoint to notify the PrSP that no further requests will be made against the flow specified by the tracking object.

## Conclude flow

Use this endpoint to request that the Payer Service Provider conclude an open flow. This signals the end of all interactions for the flow session.

##### Authorizations:

bearerAuth

##### Request Body schema: application/json
required

required | object (ActivePeSpFlowTracker) An active Payee service provider tracking object exchanged during a flow

### Responses

http://localhost:3000/conclude-flow

### Request samples

- Payload

Content type
application/json
`
{

- "tracking":
  {
  - "payerSpFlowId": "823b24e1-cccc-4276-b80b-37b3e14d05ae",
  - "payerSpMessageSeq": 1,
  - "payeeSpFlowId": "823b24e1-cccc-4276-b80b-37b3e14d05ae",
  - "payeeSpMessageSeq": 1
  }

}`

### Response samples

- 422
- default

Content type
application/json
`
{

- "statusCode": 422,
- "errorCode": "TRACKING:FLOW_NOT_FOUND",
- "title": "Semantic Error: (Unprocessable Entity) The request was well-formed but was unable to be followed due to semantic errors or business rule violations.",
- "detail": "The tracking object has mismatched flow IDs or sequence numbers",
- "instance": "0540fa0f-ccfa-4625-b006-725b4b580879",
- "extensions":
  [
  - {
    - "reason": "payerSpMessageSeq is out of sequence"
    }
  ]

}`

## Implementation Requirements

### API Extensibility

The QR+ standard makes provision for Service Providers to extend the functionality of standard API's to enable industry innovation. These extensions must, however, comply to strict requirements and not broaden a Service Providers capabilities beyond what they are legally allowed to do. SARB retains the right to disallow any extension deemed to contravene this guideline.

Any server side extension of this API MUST:

- Provide a standard compliant responses to any standard compliant call from a client
- NOT expose any additional endpoints, not specified in this standard, or implement additional or alternative APIs using the QR+ authentication and authorization framework
- Implement all endpoints and provide relevant error messages for all unsupported capabilities
- Extend functionality only by providing new discriminated union options in existing payloads
- Remain compliant to all flow reporting requirements across both standard and extended endpoints

### Flow Session Status Management

- The PrSP is the authority of flow session status. The flow session status can be either `OPEN`, `EXPIRED` or `TERMINATED` with both `EXPIRED` and `TERMINATED` being considered closed. The PrSP processes PeSP Requests only while the flow session status is `OPEN`.
- The PrSP opens a session upon receipt, and successful processing, of either (i) a PrP Paylink resolution request or (ii) a response to a PeP Paylink resolution request. The initial session expiry deadline must be 15s after the session is opened.
- The PrSP may terminate a flow session at will following, for example:
  - A Payer’s request to terminate the session
  - Loss of connectivity to the Payer
  - Processing errors
- The flow session expiry deadline is communicated through an `expiresAt` timestamp sent by the PrSP to the PeSP in the tracking object of every interaction.
- The PrSP may extend the flow session expiry deadline following, for example:
  - Receipt of an explicit request from the PeSP to do so (through the `/request-session-extension` endpoint)
  - Additional time required to interact with the Payer
  - A need to resolve ambiguous flow state. (e.g. awaiting a payment conclusion from the PeSP)
- The PrSP must not expire a flow session while there is an outstanding Reply pending to the PeSP and must extend the expiry deadline by at least 5 seconds after making the Reply call to allow the PeSP enough time to process the Reply and optionally send further Requests. The PrSP must make a Reply call to the PeSP for every Request that was accepted irrespective of flow session status.
- When a flow session is `TERMINATED` or had `EXPIRED`, the PrSP must return the final flow status with the appropriate error to all subsequent Requests for at least 240 minutes after the final status change. Thereafter the session context may be purged from the system (see Flow Record section for retention requirements).
- The PeSP must use the `expiresAt` timestamp to manage its activity but must not consider a session closed until formal confirmation to the fact is received from the PrSP.

### Recording Flow Interactions

All QR+ Service Providers MUST maintain a record of flow interactions for compliance, traceability, and operational purposes. This record also serves as the foundation for Retry idempotency (see Retry and Idempotency section).

Core Principle: All SP interactions linked to a flow MUST be captured as part of the Flow Record. This includes all ingress payloads (incoming requests and responses) and egress payloads (outgoing requests, responses and replies). All interactions, where a valid flow context exists, MUST be included in a Flow Interaction Record while Interactions (responses) where an Error is returned without a firm flow context being established (e.g. Unauthorized, Forbidden, Flow sequence violations etc.) should not be included .

#### Structure

When exposing or presenting a Flow Record, its structure must comply to the FlowInteractionRecord schema defined below. This allows consistent formatting by all flow participants and easy comparison of Records between PrSP and PeSP.

    const FlowInteractionRecord = z.object({
      payerSpFlowId: PayerSpFlowId,  // Mandatory PrSP Flow ID
      payeeSpFlowId: PayeeSpFlowId,  // Mandatory PeSP Flow ID
      extractedAt: TimeStampRo,  // Instant when record was extracted (flow may still be in progress)
      flowStatus: z.enum(['OPEN', 'EXPIRED', 'TERMINATED']),    // Flow status at instant of extraction
      interactions: z.array(z.discriminatedUnion('reqOrRes', [
        z
          .object({
            reqOrRes: z.literal('REQUEST'), // Request (from Http perspective)
            authCtx: z.object({  // JWT Authorization context
              iss: AuthzAuthorityId,  // JWT issuer
              sub: ServiceId,  // JWT source service (subscriber)
              aud: ServiceId,  // JWT target service (audience)
            }),
            loggedAt: TimeStamp,
            sourceServiceId: ServiceId,  // The service which was the source of the request
            targetServiceId: ServiceId,  // The service which was the target/destination of the request
            direction: z.enum(['PeSP<=PrSP', 'PeSP=>PrSP']),  // Direction of the request
            payload: z.discriminatedUnion('operation', [  // Any request payload from any Payer or Payee SP API as defined by API schemas
              ...PayeeProviderInteractionRequests.options,
              ...PayerProviderInteractionRequests.options,
            ]),
          })
        z
          .object({
            reqOrRes: z.literal('RESPONSE'), // Response (from Http perspective)
            loggedAt: TimeStampRo,
            sourceServiceId: ServiceId, // The service which was the source of the response
            targetServiceId: ServiceId, // The service which was the target/destination of the response
            direction: z.enum(['PeSP<=PrSP', 'PeSP=>PrSP']), // Direction of the request
            payload: z.discriminatedUnion('operation', [ // Any response from any Payer or Payee SP API as defined by API schemas (includes http status value)
              ...PayeeProviderInteractionResponses.options,
              ...PayerProviderInteractionResponses.options,
            ]),
          })
      ])),
    })

#### Other Requirements

Flow Interaction Records MUST be retained for at least five years. Flow Interaction Records serve as an audit record and MUST comply to relevant standards and practices.

### Retry and Idempotency

QR+ APIs are designed for high-reliability, low-latency server-to-server communication in a distributed peer-to-peer environment. Both PrSP and PeSP implementations must handle transient failures gracefully through Retry mechanisms while preventing duplicate processing through idempotency guarantees.

#### Client Retries

In the context of this specification, a Retry is defined as an attempt by a client to complete a network exchange that did not receive an official response from the server. This should be seen in contrast to a Re-execute which is an attempt by the client to have the server re-evaluate a previously declined request.

The client, when executing a Retry, MUST use the same tracking sequences it used in the original request. When the intent is to do a Re-execute, it MUST use a different tracking sequence with a value larger than numbers previously used within the Flow context. The server will return a response to a Retry only if the new request is considered equivalent to that originally received. (See server Idempotency requirements)

A client MAY execute a Retry on any timeout condition or Error response but SHOULD limit Retries to errors consistent with communication failures. Retries MUST NOT be captured in in Flow Record to avoid duplicate entries against a specific flow sequence.

Timeout Configuration:

- Clients MUST implement a fixed 3-second timeout for all requests
- This timeout is based on a target p99 latency of less than 1 second across the QR+ ecosystem
- Clients MUST abandon requests that exceed the timeout and treat them as failures eligible for Retry
- Service providers whose services cannot meet the 3-second timeout requirement are considered non-compliant

Circuit Breaker Policy:

- Clients MUST implement circuit breaker protection to prevent cascading failures and enable fast-fail behavior

- Circuit breaker states:

  - Closed (Normal): All requests pass through normally; error rates are tracked continuously
  - Open (Rate-Limited Probing): Requests are rate-limited to one every 3 seconds (2s + random 0-1s); on first successful probe return to closed state; failed probes keep circuit open

- Circuit breaker configuration:

  - Error Rate Threshold: Open circuit when failure rate exceeds 50% over the measurement window
  - Measurement Window: 10 seconds (track success/failure rates over rolling 10-second window)
  - Minimum Request Threshold: 5 requests minimum within window before circuit can open (prevents tripping on sporadic errors under low load)

- When a probe request during open state receives HTTP 503 with a `Retry-After` header, the circuit breaker SHOULD wait for the indicated duration before the next probe (overriding the 3-second default)

- Circuit breaker state SHOULD be tracked per target service endpoint to isolate failures

- Circuit breaker measurement window stats MUST be cleared when circuit breaker opens to avoid flapping

Retry Strategy:

- Retry logic applies AFTER circuit breaker evaluation (circuit breaker provides first layer of protection)
- Clients SHOULD Retry failed requests immediately (no delay) for the following transient error conditions:
  - HTTP 502 (Bad Gateway) - transient gateway/proxy error
  - HTTP 503 (Service Unavailable) - temporary overload or maintenance
  - HTTP 504 (Gateway Timeout) - upstream timeout
  - HTTP 408 (Request Timeout) - server-side timeout
  - HTTP 429 (Too Many Requests) - rate limiting
  - Network connectivity failures (connection refused, timeout, DNS resolution failure)
- Clients SHOULD NOT Retry for the following permanent error conditions (though servers will handle Retries with Idempotency if they occur):
  - HTTP 400 (Bad Request) - malformed request
  - HTTP 401 (Unauthorized) - authentication failure
  - HTTP 403 (Forbidden) - authorization failure
  - HTTP 404 (Not Found) - resource not found
  - HTTP 422 (Semantic Error) - business rule violation
  - HTTP 500 (Internal Server Error) - non-transient server bug/exception
  - Circuit breaker open errors (do not perform Retries while circuit breaker is open)
- Maximum of 3 total attempts per request (1 initial attempt + up to 2 retries)
- Retries occur immediately after timeout (no backoff delay) - circuit breaker provides cascade protection
- When a 503 or 429 response includes a `Retry-After` header, clients SHOULD wait for the indicated duration before attempting a Retry

Idempotency for Retries:

- Clients MUST use the tracking mechanism for idempotency rather than introducing additional headers
- When Retrying, clients MUST send an IDENTICAL request body with the same tracking values

#### Server-Side Idempotency

Servers MUST use the Flow Record as the foundation for Retry idempotency. The Flow Record serves as both a compliance record and a response cache for safe Retry handling. When a request is received which is found to be Equivalent to an interaction on the Flow Record, then the corresponding response on the Record MUST be returned.

Request Equivalence:

Servers determine request equivalence using the following criteria (all must match):

1. Authenticated Identity: Requests with successfully validated JWT where the subject (`sub`) and audience (`aud`) claims match. Retries from different issuer or with different keys are allowed to accommodate token expiry
2. Endpoint: The API operation must be identical. The operation is a combination of http path and method
3. Full Request Body: The complete request body (including tracking object) must be identical when compared as canonical JSON

Idempotency Detection and Processing:

When a request arrives, servers MUST:

1. Establish flow context: Authenticate request and retrieve/create flow context
2. Evaluate tracking sequences: Establish whether the request is new (remote message sequence larger than last received). If new, continue with request processing.
3. Retrieve Matching Interaction Record: If the request is not new, retrieve the corresponding Interaction Record from the Flow Record.
4. Evaluate match:
   - No Interaction Record found: This is a flow violation. Return `TRACKING:FLOW_SEQUENCE_VIOLATION` error response
   - Record found, request bodies match: This is a legitimate Retry. Return response from Interaction Record
   - Record found, request bodies differ: This is NOT a legitimate Retry. Return `TRACKING:FLOW_SEQUENCE_VIOLATION`

Cached responses must be available for return for a period of 240 minutes after the original request was processed. Sequence violations MUST NOT be logged to the Flow Record because they are request integrity failures, not legitimate Flow Interactions.

Handling Retries During Request Processing:

When a Retry arrives while the original request is still being processed, the Interaction Record entry will exist (request logged) but be incomplete (response not yet logged). In this scenario, servers MUST abandon the request and not send a response. Because the system cannot certify the outcome of the in-flight original request, the fail-safe approach is to trigger a client timeout for future Retry.

Session Expiry Interaction:

- Cached Retries after expiry: Servers MUST continue to return cached responses from the Flow Record for previously processed requests even after session expiry (Flow Record persists beyond session lifetime)
- New requests after expiry: For new requests (not cached Retries) received after session expiry, servers MUST return the appropriate session closure error
- Benefit: Client Retries of in-flight requests during session closure are handled correctly without client needing to track session state.

Server Error Response Guidance:

- Servers SHOULD return HTTP 503 (not 500) for temporary/transient conditions:
  - Service overload or backpressure
  - Downstream service unavailable
  - Maintenance mode
  - Resource exhaustion (connection pool full, queue full, etc.)
- Servers SHOULD include `Retry-After` header with 503 responses indicating recovery time in seconds
- Use HTTP 500 only for unexpected bugs or exceptions that may not resolve on Retry

### Bearer Token Validation

This API uses a JWT based (RFC7519), http bearer token security scheme. The JWT MUST be signed by an authorization authority registered to the QR+ Registry against a Service Provider profile. The API user identity is derived from the JWT issuer, key id and subject claim.

#### JWT claims

The JWT MUST contain the following Claims:

- `iss` : Issuer Claim containing the `authzAuthorityId` as allocated by the QR+ Registry
- `sub` : Subject Claim containing the originating (source) service id. This service must be linked to the Authorization Authority identified by `iss`.
- `aud` : Audience Claim containing the target service id this token was created to access. The target service MUST be complementary to the source service: same `flowType` (e.g. `pep-general`) and opposite `kind` (one `PAYER`, one `PAYEE`). For example, a `pep-general` `PAYEE` source service may only target a `pep-general` `PAYER` service.
- `exp` : Expiration time Claim defining a time of expiry for this token
- `permissions` : An array of token permissions for authorizations from:
  - `enrich` - Can execute enrichment flows against the SP APIs

#### JWT header

The JWT header must contain the following parameters:

- `alg` : The JOSE algorithm used for JWT generation
- `kid` : The key id of the authorization authority key used to generate the JWT

#### JWT Validation

Implementers of this API must satisfy the following minimum requirements when validating a presented JWT:

- Best Practice: Follow best practices as outlined in RFC8725 and related standards
- User Identity: The API user identity MUST be determined as the service (`sub`) only when (i) the Issuer claim (`iss`) matches an Authorization Authority for this service (linked by the Registry) and (ii) The key id of the key used matches a key id used by the authorization authority (`kid` header parameter)
- Algorithms: JWT MUST fail if the `alg` property in the header is set to 'none' or if it does not align with the key type of the referenced Authorization Authority
- Audience validation: The service id referenced in the `aud` Claim MUST match the service id of the service being accessed.
- Complementary services: The source service (`sub`) and target service (`aud`) MUST be complementary — both services MUST exist on the Registry Cache, share the same `flowType`, and have opposite `kind` (one `PAYER`, one `PAYEE`). A token where source and target are not complementary (e.g. both `PAYER`, or different `flowType`) MUST be rejected with HTTP 403 Forbidden. This guarantees that only services intended to interact with each other can authenticate against each other.
- Action flowType authorization: For Paylink resolution, the target service (`aud`) MUST have a `flowType` equal to the flowType decoded from the Paylink's `flowTypeIndicator`. A request whose target service's flowType does not match the Paylink's flowType MUST be rejected with HTTP 403 Forbidden. This prevents a token issued for a service of one flow type from being used to act on a Paylink of a different flow type (the complementary rule above guarantees the source service's flowType matches transitively).

#### Flow Participation

For all post-Paylink-resolution endpoints (i.e. every endpoint other than Paylink resolution), the calling service MUST be a participant in the flow being acted upon. Implementers MUST enforce:

- The source service (`sub`) MUST match the flow's recorded remote service id, AND
- The target service (`aud`) MUST match the flow's recorded local service id.

A request whose JWT identifies services that are not the recorded participants of the flow MUST be rejected with HTTP 403 Forbidden, irrespective of whether the request would otherwise be a new request or a legitimate Retry. This ensures that, for example, only the same source service that resolved a Paylink may subsequently issue requests against that flow.

## Glossary

Term | Description
--- | ---
Acceptance Option | A payload specifying a payment rail supported by a Payee for payment. (Option Payload)
Anchor Domain | The common domain used for all Paylink web URIs, allowing deep linking through officially registered mobile applications.
Authorization Authority | An entity, managed on the QR+ Registry, which represents a Service Provider IdP that issues bearer tokens to authenticate API calls. A Service Provider manages Authorization Authorities according to their governance and security policies.
Extended Number Encoded Paylink | A 27-character alphanumeric Paylink format (ZA-FTI-SPII-PLV) optimized for Code128 barcode scanning with extended PLV namespace for medium lifespan experiences.
Facilitated Experience | The experience where the Scanner uses a Paylink Facilitator Application to start a QR+ Flow.
Flow | A Flow is the set of actions executed between a PrSP and PeSP as part of one QR+ session.
Flow Record | An audit trail record, created by both PrSP and PeSP, capturing all Service Provider Interactions exchanged as part of a unique Flow.
Flow Session | A time-bound session that tracks the lifecycle of a Flow with states OPEN, TERMINATED, or EXPIRED. A PrSP only process PeSP Requests while a Flow Session is OPEN.
Flow Tracker | A structured object within Service Provider Interactions containing Flow identifiers and Message Sequence Numbers to coordinate and track messages within a Flow.
Flow Type | A classification of QR+ Flows which govern use case requirements, Paylink lifespan constraints, supported encodings, and security requirements (e.g., pep-general, pep-shortcode, prp-connected, prp-disconnected). Service Providers are licensed for individual Flow Types.
Flow Type Indicator | An encoding-specific identifier that indicates which Flow Type is being used (e.g., 'general' representing the pep-general Flow Type in URI encoded Paylinks and '1' representing the prp-shortcode Flow TYpe in number encoded Paylinks).
Identity Request | A QR+ Request for Payer identity information initiated by the PeSP on behalf of the Payee.
Loyalty Request | A QR+ Request for Payer loyalty membership information initiated by the PeSP on behalf of the Payee.
Message Sequence Number | An incrementing counter maintained by each Service Provider to track the ordering and uniqueness of messages sent within a Flow. Used for audit, idempotency and replay detection.
Number Encoded Paylink | A 13-digit numeric Paylink format (FTI-SPII-PLV-Luhn) optimized for human reproduction via voice or manual keypad entry with built-in error detection.
Payee (actor) | A party who interacts with a Paylink with the intention of receiving payment. The Payee can be either a Scanner or a Presenter depending on the use case.
Payee Service Provider (PeSP) | A registered organization that executes QR+ enrichment flows on behalf of a Payee. The PeSP may deploy Paylink Provider and/or Paylink Facilitator functionality depending on its supported experiences.
Payer (actor) | A party who interacts with a Paylink with intention to pay. The Payer can be either a Scanner or a Presenter depending on the use case.
Payer Service Provider (PrSP) | A registered organization that executes QR+ enrichment flows on behalf of a Payer. The PrSP may deploy Paylink Provider and/or Paylink Facilitator functionality depending on its supported experiences.
Paylink | A structured identifier, encoded and presented in various formats, used by Payers and Payees to initiate a financial interaction.
Paylink actioning | The Scanner scanning the Paylink and submitting it to its Service Provider for processing.
Paylink Encoding Format | A Paylink Encoding Format is a specific way the Paylink attributes are encoded. For example, URI encoded format.
Paylink Facilitator (role) | A PrSP or PeSP organization registered to process Paylinks for Scanners.
Paylink generation | An exchange between the Presenter (Payee or Payer) and the Service Provider (PeSP or PrSP) to generate a new Paylink.
Paylink presentment | The Presenter presenting the Paylink to the Scanner for scanning.
Paylink Presentment Format | A Paylink presentment format specifies the technology used to present the Paylink for scanning. For example, QR code, Barcode and web click.
Paylink Provider (role) | A PrSP or PeSP registered to create Paylinks for Presenters.
Paylink Quarantine Period | The period, after a Paylink has been cancelled, during which the PLV must not be used for a new Paylink.
Paylink resolution | The Paylink Facilitator presenting the Paylink to the Paylink Provider to initiate a QR+ flow.
Paylink Value (PLV) | The cryptographically secure random identifier component within a Paylink that uniquely identifies a specific Paylink instance.
Payment Confirmation | A rail-specific payload passed by the PrSP to the PeSP confirming successful initiation of a Push Payment, without claiming settlement status.
Payment Conclusion | A rail-specific payload passed by the PeSP to the PrSP to provide its view on the outcome of a Payment Request.
Payment Delegation | A rail-specific payload passed by the PrSP to the PeSP containing Pull Payment credentials and transaction details, delegating payment initiation to the PeSP.
Payment Request | A QR+ Request for payment initiated by the PeSP on behalf of the Payee.
Presenter (role) | A Payer or Payee who presents a Paylink to a Scanner party to action. The Presenter interacts with her Service Provider to create the Paylink according to her requirements.
Pull Payment | A payment mechanism that is initiated by a PeSP.
Push Payment | A payment mechanism that is initiated by a PrSP.
QR+ Flow Orchestration | The process of coordinating actions between the PrSP, PeSP, Payer and Payee to achieve a successful transaction outcome.
QR+ Registry Service | A service operated by NPU to enable service discovery of registered Service Providers and Services.
QR+ Service Provider | An organization registered to take part in QR+ flows on behalf of Payers and/or Payees.
Scanner (role) | A party who actions a Paylink by submitting it to their chosen Paylink Facilitator.
Service Provider Indicator | An encoding-specific identifier, assigned by SARB to a registered Service Provider, which allows all Service Providers to identify them as the creator of a Paylink.
Service Provider Interaction | A request and response exchange between a PrSP and a PeSP during a Flow, captured for audit trail, Flow analysis, and idempotency purposes.
Signed CBOR Encoded Paylink | A cryptographically signed Concise Binary Object Representation (CBOR) Paylink format using ECDSA signatures for high-security use cases requiring cryptographic attestation.
URI Encoded Paylink | A non-URL, RFC3986-compliant URI format for Paylinks (`qr-plus://<anchor domain>/v1/<flowTypeIndicator>/<spIndicator>/<plv>`) supporting deep linking and extended lifespans.