Open Finance Malaysia - Data Consumer Documentation Portal

Download OpenAPI specification:Download

Overview

The PayNet Open Finance Platform (OFP) is a Financial-grade API (FAPI) platform based on OpenID Connect and OAuth 2.0 standards. It enables secure, user-consented sharing of financial data between institutions.

There are three main parties involved:

  1. Data Consumer (DC): Your application, requests financial data on behalf of an end user (e.g. for PFM or lending use cases)
  2. Data Provider (DP): The institution that holds the user's financial data (e.g. a bank)
  3. PayNet OFP: The intermediary platform that manages consent, authentication, and secure data exchange

PayNet OFP exposes two key server components:

  • Authorization Server (AS): manages user consent, authentication, and issues access tokens
  • Resource Server (RS): serves financial data (accounts, balances, transactions) when a valid access token is presented

Understanding the Journey

Before you start, you can simulate the User Experience as an End User starting from DC Simulator, Paynet Aggregator, and DP Simulator, for Consent Initiation up to Retrieve Balance Flow.

DC consent initiation screen

Run this Journey

  1. Access Paynet DC Simulator at https://dc-sim.uat.inet.ofm.my
  2. Start the Linking Account Process as per the UX Flow above
  3. DP Login credentials are:
    • Username: paynet
    • Password: paynet

Configuration & Pre-Requisite as a Data Consumer

In this period, PayNet will support manual onboarding for Banks into PayNet Open Finance Platform.

Step 1: Submit Your Security Certificates and Redirect URL

PayNet OFP uses multiple certificate-based security protocols. You must submit the appropriate certificates for each protocol.

Protocols requiring certificates:

Protocol Purpose
mTLS Mutual TLS, authenticates both client and server at the transport layer
JWS JSON Web Signature, signs API request/response payloads to prevent tampering
JAR JWT-Secured Authorization Request, secures the PAR authorization request
JWE JSON Web Encryption, encrypts sensitive financial data in API responses

How to Submit the Certificates and Redirection URL:

Send email to PayNet xxxxxxxxx. (PayNet to prepare Instruction)

Step 2: Get Your Client Credentials & Authorization Code

Client Credentials are used for machine-to-machine (M2M) calls specifically, when your DC system calls PayNet without direct end-user involvement (e.g. consent management operations).

How to Get these Credentials:

After submitting your Security Certificates and Redirection URL, you will receive an email reply containing your credentials the next business day.

What You Need to Build

User Interface Requirements

Your DC application must provide a user-facing interface that guides the end user through the account linking and consent journey. At minimum, this should include:

  • Account Linking Screen: Entry point where the user initiates the account linking process
  • Data Provider Selection Screen: Allows the user to choose which bank/institution to link
  • Consent Review Screen: Clearly presents what data is being requested and for how long, before submission
  • Consent Confirmed Screen: shown after successful authorization, confirming the accounts linked
  • Consent Rejected Screen: shown if the user declines or an error occurs during authorization
  • Account Data Display: displays retrieved financial data (e.g. balance) once consent is active

Refer to the sample of UI mockup provided below for the expected screen flows as a Data Consumer:

DC consent initiation screen

Positive Scenario: End User Approves Consent

DC consent initiation screen

Negative Scenario: End User Rejects/Cancels Consent

Backend API Integration

Your backend must implement the following endpoints:

  1. All Authorization Server Endpoints
  2. Resource Server Endpoints, for Drop 1:
    • Providers
    • Retrieve Consent
    • Account Balance
  3. Consent Webhook receiver

API Integration Guide

Obtaining an Access Token (Client Credentials Grant)

Use this flow when your DC backend needs to call PayNet on its own behalf, for example, to retrieve consent information or perform consent lifecycle management (get, revoke).

Endpoint:

POST https://{AS_URL}/v1/oauth/token
Content-Type: application/x-www-form-urlencoded
x-fapi-interaction-id: <UUID>

Request Parameters:

Parameter Required Description
grant_type Must be client_credentials
client_id The Client ID obtained from PayNet

🔒 This request must be made over mTLS using the certificate bound to your client_id.

curl --location --request POST 'https://{AS_URL}/v1/oauth/token' \
  --header 'Content-Type: application/x-www-form-urlencoded' \
  --header 'x-fapi-interaction-id: <UUID>' \
  --cert /path/to/client.crt \
  --key /path/to/client.key \
  --data-urlencode 'grant_type=client_credentials' \
  --data-urlencode 'client_id=<YOUR_CLIENT_ID>'

Successful Response (200):

PayNet returns an access_token. Use this token as a Bearer token in subsequent M2M API calls (e.g. consent retrieval, consent revocation).

Calling Resource APIs

Once you have a valid access_token, you can call the Resource Server to retrieve financial data.

Sequence Overview

DC Sequence Overview

Retrieve Consent

Retrieve the existing consent record using Consent ID of the account linked. DC may use this endpoint to obtain the list of accounts associated with that consent, before then able to retrieve the balance data of each accounts.

GET https://{RS_URL}/v1/consents/{consent_id}
Authorization: Bearer {access_token}
x-fapi-interaction-id: <UUID>
x-signature: <JWS signed header>
x-enc-kid: <KID of your DC encryption certificate>
Header Description
Authorization Bearer token, the access_token
x-fapi-interaction-id A unique UUID for request tracing and correlation
x-signature A JWS-signed representation of the request, signed with your DC JWS certificate
x-enc-kid The Key ID (KID) of your DC encryption (JWE) certificate, tells the DP which key to use when encrypting the response

🔒 This request must be made over mTLS using the certificate bound to your client_id.

curl -X GET 'https://rs.paynet-ofp.com/v1/consents/c8f1a2b3-4d5e-6f7a-8b9c-0d1e2f3a4b5c' \
  --cert   'dc-client.crt' \
  --key    'dc-client.key' \
  --cacert 'paynet-ca.crt' \
  -H 'x-fapi-interaction-id: f47ac10b-58cc-4372-a567-0e02b2c3d479' \
  -H 'authorization: Bearer eyJhbGciOiJQUzI1NiIsImtpZCI6ImRjLXNpZy0wMSJ9..<access_token>' \
  -H 'x-fapi-signature: eyJhbGciOiJQUzI1NiIsImtpZCI6ImRjLXNpZy0wMSIsImlzcyI6ImRjLTEyMyIsImp0aSI6ImY0N2FjMTBiLTU4Y2MtNDM3Mi1hNTY3LTBlMDJiMmMzZDQ3OSIsImlhdCI6MTcwOTYwNjQwMH0..<signature>'

Get Account Balance Endpoint:

GET https://{RS_URL}/v1/accounts/{account_id}/balances
Authorization: Bearer {access_token}
x-fapi-interaction-id: <UUID>
x-signature: <JWS signed header>
x-enc-kid: <KID of your DC encryption certificate>

Key Request Headers:

Header Description
Authorization Bearer token, the access_token
x-fapi-interaction-id A unique UUID for request tracing and correlation
x-signature A JWS-signed representation of the request, signed with your DC JWS certificate
x-enc-kid The Key ID (KID) of your DC encryption (JWE) certificate, tells the DP which key to use when encrypting the response

🔒 This request must be made over mTLS using the certificate bound to your client_id.

curl --location --request GET 'https://{RS_URL}/v1/accounts/{account_id}/balances' \
  --header 'Authorization: Bearer <ACCESS_TOKEN>' \
  --header 'x-fapi-interaction-id: <UUID>' \
  --header 'x-signature: <SIGNED_JWS>' \
  --header 'x-enc-kid: <DC_ENCRYPTION_CERT_KID>' \
  --cert /path/to/client.crt \
  --key /path/to/client.key

Response:

The response is returned as a JWE (encrypted) payload wrapped inside a JWS (signed) envelope. Your DC must:

  1. Verify the JWS signature using the DP's public certificate
  2. Decrypt the JWE payload using your DC's private encryption key

Balance Object fields include:

Field Description
account_id Unique identifier for the linked account
current_balance Real-time balance excluding pending transactions

💡 The same pattern applies to other Resource APIs later: GET Account and GET Account Transactions follow the same authentication, signing, and decryption requirements.

Testing Guide

Test Your DC Against Mock Data Provider

This is your primary integration test and should be completed before any live DP pairing. It uses PayNet's pre-built Mock DP, allowing you to test the full end-to-end flow in a controlled environment.

Pre-work Checklist

Before starting, confirm the following are complete:

  • All prerequisites steps have been completed (certificates uploaded, credentials generated)
  • Your DC application has been built for your target use case (PFM or Lending) based on API Spec v1.2.1
  • Your redirect URL is correctly registered and accessible

End-to-End Test Journey

The following table describes each step of the test, which party drives it, and the expected outcomes as per UI, Backend API, and API Integration Guide in the previous sections. You can focus on the bold rows for your DC testing purpose.

Step Actor Action UI Expected Outcome Backend Expected Outcome from DC
1 Real DC User navigates to the account linking screen Linking screen displayed (Handled by Frontend)
2 Real DC User selects a Data Provider from the list DP selection Call /providers
3 Real DC User reviews consent details on DC UI Consent review screen displayed as per request (Handled by Frontend)
4 Real DC User confirms and submits consent User is redirected to Mock DP UI Call /par
Redirect to PN /authorize
5 Mock DP User is redirected to the Mock DP login page Mock DP login screen displayed -
6a Mock DP User logs in with valid preset credentials (positive) Login succeeds; account selection screen displayed -
6b Mock DP User enters incorrect password (negative) Login fails; error message displayed -
7 Mock DP User selects one or more accounts to link Accounts selected -
8 Mock DP User reviews consent at the DP side Consent review screen displayed -
9a Mock DP User approves consent (positive) User redirected to DP success page -
9b Mock DP User rejects consent (negative) User redirected back to DC with rejection error -
10 Mock DP User gets Success confirmation and click Back to DC User redirected back to DC with authorization_code Handle the Callback
11a Real DC Consent approved callback received DC retrieves and displays account balance DC exchanges code for token /token, Call /balance
11b Real DC Consent rejected callback received DC displays "Account not linked" message to user Update status

Definition of Done

Your integration is considered complete for this phase when all of the following have been demonstrated with your Certificates and Credentials:

  • Consent initiated successfully: Your DC can initiate a consent request and receive an OAuth authorization code from PayNet
  • Tokens issued: Your DC can exchange the authorization code for access_token, refresh_token, and id_token
  • Resource API functional: Your DC can call at least one Resource API (e.g. Get Balance) using a valid access_token and successfully decrypt and display the response

Test Your DC Against a Real Data Provider

This phase tests interoperability with a ready DP in a controlled bilateral pairing session.

⚠️ Important prerequisite: Your bank must have also passed testing as a Data Provider (DP) before this phase can proceed.

How to proceed:

Once the above prerequisite is met, contact PayNet to schedule and align a bilateral testing session with a Real DP. PayNet will facilitate the pairing and provide guidance on the specific test scenarios to execute.

Error Codes

HTTP Status Codes

Code Description
200 OK
201 Created
204 No Content
303 Redirect
400 Bad Request
401 Unauthorized
403 Forbidden
404 Not Found
405 Method Not Allowed
429 Too Many Requests
500 Internal Server Error
503 Service Unavailable

OAuth 2.0 Error Codes

Returned in error responses from authorization and token endpoints.

Code Description
invalid_request Missing or invalid parameter.
invalid_client Client authentication failed.
invalid_grant Invalid, expired, or revoked grant/code.
unauthorized_client Client not authorized for this grant type.
unsupported_grant_type Grant type not supported.
access_denied Resource owner or AS denied the request.
invalid_scope Invalid or unknown scope.
server_error Unexpected server-side condition.
temporarily_unavailable Server overloaded or in maintenance.

Account & Application Error Codes

Code Description
Consent.AccountTemporarilyBlocked Account temporarily blocked by DP business rule.
Consent.PermanentAccountAccessFailure Account permanently unavailable.
Consent.TransientAccountAccessFailure Account unavailable due to transient failure.
AccessToken.InvalidScope Token does not have required scope.
Consent.Invalid Consent is in an invalid state.
Consent.BusinessRuleViolation DP business rule prevents the operation.
JWS.InvalidSignature JWS signature verification failed.
JWS.InvalidClaim One or more JWS claims failed validation.
JWE.DecryptionError JWE decryption failure.

Error Response Format

Error Response Body

{
  "error": "invalid_grant",
  "error_description": "The authorization code has expired."
}

Rate Limiting

When receiving HTTP 429 (Too Many Requests), implement retry with exponential backoff to avoid overwhelming the platform.

Attempt Wait Time
Initial Immediate
Retry 1 5 seconds
Retry 2 10 seconds
Retry 3 20 seconds
Retry 4 40 seconds

Authorization Server

The PayNet Open Finance Platform exposes a FAPI 2.0–compliant Authorization Server (AS) for OAuth 2.0 and OpenID Connect flows. Integrators should use discovery to obtain all AS endpoints and metadata instead of hardcoding URLs.

For this environment the AS issuer is https://api.as.openfinance.dev.inet.paynet.my.

Using the discovery (issuer) endpoint

  1. Start from the issuer URL You will receive the AS base URL (issuer) for your environment (e.g. from the Partner Dashboard or onboarding). The issuer is the logical identifier of the AS and must match the iss claim in tokens and authorization responses.

  2. Fetch the discovery document Request the OpenID Connect / OAuth 2.0 metadata at:

    • https://api.as.openfinance.dev.inet.paynet.my/.well-known/openid-configuration This document (see OpenID Connect Discovery 1.0 and RFC 8414) contains:
    • issuer — the AS issuer URL (validate that it matches the iss in responses and tokens).
    • authorization_endpoint, token_endpoint, jwks_uri, pushed_authorization_request_endpoint, userinfo_endpoint, and optionally introspection_endpoint, revocation_endpoint.
    • Supported algorithms, scopes, response types, and other capabilities.
  3. Use the URLs from the discovery response Use these values for all subsequent requests (authorization, token exchange, PAR, UserInfo, JWKS). Do not hardcode paths; environments may differ.

Well-Known OpenID Configuration

Responses

Response samples

Content type
application/json
{
  • "issuer": "string",
  • "jwks_uri": "string",
  • "pushed_authorization_request_endpoint": "string",
  • "authorization_endpoint": "string",
  • "token_endpoint": "string",
  • "userinfo_endpoint": "string",
  • "introspection_endpoint": "string",
  • "revocation_endpoint": "string",
  • "mtls_endpoint_aliases": {
    },
  • "response_types_supported": [
    ],
  • "id_token_signing_alg_values_supported": [
    ],
  • "scopes_supported": [
    ],
  • "claims_supported": [
    ],
  • "token_endpoint_auth_methods_supported": [
    ],
  • "code_challenge_methods_supported": [
    ],
  • "response_modes_supported": [
    ],
  • "tls_client_certificate_bound_access_tokens": true,
  • "authorization_details_types_supported": [
    ],
  • "require_pushed_authorization_requests": true,
  • "request_object_signing_alg_values_supported": [
    ]
}

Authorization

query Parameters
client_id
required
string <= 36 characters

Client ID

request_uri
required
string <= 300 characters

A URN (Uniform Resource Name) that identifies the authorization request the client just pushed. The AS will use this request_uri to retrieve the original, complete authorization request.

Responses

Introspection

header Parameters
x-fapi-interaction-id
string <uuid>

A UUID used as a correlation id

Request Body schema: application/x-www-form-urlencoded
token
required
string <= 36 characters

The access token or refresh token to be introspected.

token_type_hint
required
string <= 32 characters
Enum: "access_token" "refresh_token"

A hint about the type of the token submitted.

Responses

Response samples

Content type
application/json
{
  • "active": true,
  • "scope": "string",
  • "client_id": "string",
  • "token_type": "string",
  • "sub": "string",
  • "iss": "string",
  • "aud": "string",
  • "nbf": 0,
  • "exp": 0,
  • "iat": 0,
  • "jti": "string",
  • "cnf": {
    }
}

JSON Web Key Set (PayNet)

Responses

Response samples

Content type
application/json
{
  • "keys": [
    ]
}

JSON Web Key Set (DP)

path Parameters
dp_id
required
string

Distribution Partner identifier

Responses

Response samples

Content type
application/json
{
  • "keys": [
    ]
}

Pushed Authorization Request (PAR)

header Parameters
x-fapi-interaction-id
string <uuid>

A UUID used as a correlation id

Request Body schema: application/x-www-form-urlencoded
client_id
required
string <= 36 characters

Client ID

request
required
string <= 3000 characters

JAR (JWT Secured Authorization Request) signed using the client's private key. It contains the authorization request parameters. Sample payload (claims inside the JWT, values show data types):

{
  "iss": "string",
  "aud": "string",
  "nbf": integer,
  "exp": integer,
  "iat": integer,
  "jti": "string",
  "client_id": "string",
  "response_type": "string",
  "redirect_uri": "string",
  "scope": "string",
  "state": "string",
  "code_challenge": "string",
  "code_challenge_method": "string",
  "response_mode": "string",
  "authorization_details": [
    {
      "type": "string",
      "consent": {
        "dc_id": "string",
        "dp_id": "string",
        "consent_type": "string",
        "consent_purpose": "string",
        "permissions": ["string"],
        "expiration_datetime": "datetime"
      }
    }
  ],
  "acr_values": "string"
}

Responses

Response samples

Content type
application/json
{
  • "request_uri": "string",
  • "expires_in": 0
}

Revocation

header Parameters
x-fapi-interaction-id
string <uuid>

A UUID used as a correlation id

Request Body schema: application/x-www-form-urlencoded
token
required
string <= 36 characters

The token that the client wants to get revoked.

token_type_hint
required
string <= 32 characters
Enum: "access_token" "refresh_token"

A hint about the type of the token submitted (e.g. access_token, refresh_token).

Responses

Token

header Parameters
x-fapi-interaction-id
string <uuid>

A UUID used as a correlation id

Request Body schema: application/x-www-form-urlencoded
grant_type
required
string <= 32 characters
Enum: "client_credentials" "authorization_code" "refresh_token"

Grant type: client_credentials (M2M), authorization_code (user flow), or refresh_token (obtain new access tokens).

client_id
required
string <= 36 characters

Client ID

scope
string <= 100 characters

The scope of the access token being requested (e.g. accounts). Optional; for authorization_code/refresh_token, if specified must be equal to or a subset of the original scope.

code
string <= 36 characters

Authorization code grant only. The authorization code issued by the Authorization Server.

redirect_uri
string <= 300 characters

Authorization code grant only. The redirect_uri that was used in the original authorization request.

code_verifier
string <= 128 characters

Authorization code grant only. The PKCE code verifier.

refresh_token
string <= 36 characters

Refresh token grant only. The refresh token previously issued by the Authorization Server.

Responses

Response samples

Content type
application/json
{
  • "access_token": "string",
  • "token_type": "Bearer",
  • "expires_in": 0,
  • "scope": "string",
  • "id_token": "string",
  • "refresh_token": "string",
  • "authorization_details": [
    ]
}

User Info

header Parameters
x-fapi-interaction-id
string <uuid>

A UUID used as a correlation id

authorization
required
string

Bearer {access_token}

Responses

Response samples

Content type
application/json
{
  • "name": "string",
  • "sub": "string",
  • "given_name": "string",
  • "family_name": "string",
  • "preferred_username": "string",
  • "picture": "string",
  • "email": "string",
  • "email_verified": true,
  • "id_type": "nric",
  • "hashed_id_number": "string"
}

Resource Server

Account Balances

DC retrieves an account balances information from DP via PayNet.

Authorizations:
path Parameters
account_id
required
string

AccountID is the unique identifier for the account.

Responses

Response samples

Content type
application/json
{
  • "data": {
    }
}

Retrieve Consent

DC retrieves an existing consent using the consent ID. The DC may also use this endpoint to obtain the list of accounts associated with that consent.

Authorizations:
path Parameters
consent_id
required
string

ConsentID is the unique consent identifier (path parameter).

Responses

Response samples

Content type
application/json
{
  • "data": {
    }
}

Get Providers

List providers (directory). Returns providers with pagination.

query Parameters
page_size
integer <int32>

PageSize is the number of records per request (optional, default 3 per spec).

next_page_params
string

NextPageParams is the URL-encoded parameter string for the next page (300 chars). Required when requesting a subsequent page.

Responses

Response samples

Content type
application/json
{
  • "data": [
    ],
  • "meta": {
    }
}