Consent — API Guide 3 min read
In UAE Open Finance, a Consent is a structured, user-authorized agreement that grants a TPP specific rights to access data or initiate payments on a user's behalf. All API access is consent-bound — you cannot call a resource endpoint without a valid, authorized consent.
Consents are created through the Pushed Authorization Request flow (FAPI 2.0 PAR). Rather than creating a consent resource directly, the TPP embeds the consent definition inside a signed Request JWT and pushes it to the Authorization Server. The user then authenticates at the LFI and explicitly authorizes the consent.
Consent types
| Type | authorization_details.type | Used for |
|---|---|---|
| Bank Data Sharing | urn:openfinanceuae:account-access-consent:v2.1 | Reading account information, balances, transactions |
| Service Initiation | urn:openfinanceuae:service-initiation-consent:v2.1 | Initiating domestic payments |
| Insurance Data Sharing | urn:openfinanceuae:insurance-consent:v2.1 | Reading insurance policy details |
API sequence flow
POST /par
/par Push the signed Request JWT to the Authorization Server. The authorization_details inside the JWT carries the full consent definition — account permissions, payment amounts, billing details, and (for payments) encrypted PII.
// PAR endpoint is read from .well-known/openid-configuration —
// not constructed from the issuer URL (it lives on a different host).
const PAR_ENDPOINT = discoveryDoc.pushed_authorization_request_endpoint
const parResponse = await fetch(PAR_ENDPOINT, {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: new URLSearchParams({
request: requestJWT, // signed Request JWT containing authorization_details
client_assertion_type: 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer',
client_assertion: await buildClientAssertion(),
}),
// agent: new https.Agent({ cert: transportCert, key: transportKey }),
})
const { request_uri } = await parResponse.json() For the full construction of authorization_details — including field tables, PII encryption, and code examples — see the specific API guides, for example:
See Preparing the Request JWT for how to build and sign the Request JWT, and POST/par for the full API reference.
Redirecting the user
Build the authorization URL using the authorization_endpoint from the LFI's .well-known/openid-configuration and the request_uri returned by /par:
// authorization_endpoint from .well-known/openid-configuration
// Each LFI sets its own path — there is no fixed structure
// e.g. on the altareq1 sandbox: 'https://auth1.altareq1.sandbox.apihub.openfinance.ae/auth'
const AUTHORIZATION_ENDPOINT = discoveryDoc.authorization_endpoint
const authCodeUrl = `${AUTHORIZATION_ENDPOINT}?client_id=${CLIENT_ID}&response_type=code&request_uri=${encodeURIComponent(request_uri)}`
window.location.href = authCodeUrl
// or server-side: res.redirect(authCodeUrl)The user will authenticate with their bank and authorize the consent on the LFI's authorization screen.
Handling the callback
After authorization, the LFI redirects the user back to your redirect_uri:
https://yourapp.com/callback?code=fbe03604-baf2-4220-b7dd-05b14de19e5c&state=d2fe5e2c-77cd-4788-b0ef-7cf0fc8a3e54&iss=https://auth1.altareq1.sandbox.apihub.openfinance.ae Always validate state and iss before proceeding. See Handling Authorization Callbacks for the full security guide.
POST /token
/token Exchange the authorization code for an access token and refresh token. The code_verifier must match the code_challenge sent in the Request JWT (PKCE).
// Token endpoint is read from .well-known/openid-configuration —
// not constructed from the issuer URL (it lives on a different host).
const TOKEN_ENDPOINT = discoveryDoc.token_endpoint
const tokenResponse = await fetch(TOKEN_ENDPOINT, {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: new URLSearchParams({
grant_type: 'authorization_code',
code,
redirect_uri: REDIRECT_URI,
code_verifier: codeVerifier,
client_assertion_type: 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer',
client_assertion: await buildClientAssertion(),
}),
// agent: new https.Agent({ cert: transportCert, key: transportKey }),
})
const { access_token, refresh_token, expires_in } = await tokenResponse.json() The access token is consent-bound — it carries the scope and ConsentId granted during authorization. See Tokens & Assertions for token lifetimes and the refresh flow.
When obtaining an access token you also receive the current state of the consent (including the status) to confirm it has moved to the Authorized state before making resource API calls.
Maintaining consent state
After a consent is created, your application needs to track its status over time. There are two approaches:
Subscribe to webhook events Recommended
When a consent is created with subscription.Webhook.IsActive: true, on every consent status change — for example, when a user revokes, or the consent expires — the API Hub delivers a Consent Status Event to your registered webhook URL. This avoids the need to poll and ensures your application reacts to status changes in real time.
Note: as Events are delivered as JWEs, this approach requires a valid Encryption Certificate on your Application. See the Consent Status Event — API Guide for the full flow.
Poll the consent endpoint
If you need to check the current state of a consent on demand, call the consent endpoint directly. Both endpoints require a client credentials access token — not the user's consent-bound access token.
Obtaining a client credentials token
const params = new URLSearchParams({
grant_type: 'client_credentials',
scope: 'openid accounts', // or 'openid payments' for service initiation
client_assertion_type: 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer',
client_assertion: await buildClientAssertion(),
})
// Reuse the TOKEN_ENDPOINT discovered above (discoveryDoc.token_endpoint).
const tokenResponse = await fetch(TOKEN_ENDPOINT, {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: params.toString(),
// agent: new https.Agent({ cert: transportCert, key: transportKey }),
})
const { access_token } = await tokenResponse.json()Bank Data Sharing
const LFI_API_BASE = process.env.LFI_API_BASE_URL!
const consentResponse = await fetch(
`${LFI_API_BASE}/open-finance/v2.1/account-access-consents/${consentId}`,
{
headers: { Authorization: `Bearer ${access_token}` },
// agent: new https.Agent({ cert: transportCert, key: transportKey }),
}
)
const { Data: { Status, Permissions, ExpirationDateTime } } =
await consentResponse.json()
if (Status !== 'Authorized') {
throw new Error(`Consent not authorized: ${Status}`)
} See GET/account-access-consents/{ConsentId} for the full response schema.
You can also retrieve all consents created under a long-lived base consent by passing baseConsentId as a query parameter to GET/account-access-consents.
Service Initiation
const consentResponse = await fetch(
`${LFI_API_BASE}/open-finance/v2.1/payment-consents/${consentId}`,
{
headers: { Authorization: `Bearer ${access_token}` },
// agent: new https.Agent({ cert: transportCert, key: transportKey }),
}
)
const { Data: { Status, ControlParameters, ExpirationDateTime } } =
await consentResponse.json()
if (Status !== 'Authorized') {
throw new Error(`Consent not authorized: ${Status}`)
} See GET/payment-consents/{ConsentId} for the full response schema.
You can also retrieve all payment consents under a long-lived base consent by passing baseConsentId as a query parameter to GET/payment-consents.
Insurance Data Sharing
const consentResponse = await fetch(
`${LFI_API_BASE}/open-finance/v2.1/insurance-consents/${consentId}`,
{
headers: { Authorization: `Bearer ${access_token}` },
// agent: new https.Agent({ cert: transportCert, key: transportKey }),
}
)
const { Data: { Status, Permissions, ExpirationDateTime } } =
await consentResponse.json()
if (Status !== 'Authorized') {
throw new Error(`Consent not authorized: ${Status}`)
} See GET/insurance-consents/{ConsentId} for the full response schema.
You can also retrieve all insurance consents under a long-lived base consent by passing baseConsentId as a query parameter to GET/insurance-consents.
A consent moves through a defined lifecycle — AwaitingAuthorization → Authorized → Consumed / Expired / Revoked. See Consent Overview for the full state machine and transition rules.
