LFI · Consent Journey · API Guide

Consent Journey — API Guide 6 min read

End-to-end implementation guide for the consent authorization journey: from the TPP's POST/par through your Authorization Endpoint, the Headless Heimdall and Consent Manager API calls, and the final doConfirm/doFail redirect back to the TPP.

01 Prerequisites

What must be in place before you start

Before implementing the consent journey, ensure the following are in place:

Required API implementations

You MUST implement the following endpoints:

EndpointDirectionPurpose
GET/authLFI → API HubInitiate the authorization interaction
GET/consents/{consentId}LFI → API HubRetrieve the full consent details
PATCH/consents/{consentId}LFI → API HubUpdate consent status, end user identifiers, and account IDs
POST/auth/{interactionId}/doConfirmLFI → API HubComplete the authorization interaction and redirect back to TPP successfully
POST/auth/{interactionId}/doFailLFI → API HubComplete the authorization interaction and redirect back to TPP with a failure
02 API sequence flow

End-to-end flow diagram

Sequence diagramConsent FlowClick to expand
04 End user redirect and authorization interaction

Steps 4–6 — receive redirect, GET /auth, GET /consents

Step 4 — End user is redirected to your Authorization Endpoint

After the consent is created via POST/par, the TPP redirects the end user to your Authorization Endpoint with the following query parameters:

text
https://your-auth-endpoint.example.com/authorize?client_id={clientId}&response_type=code&request_uri={request_uri}

Where request_uri is the value returned from the /par response. Your Authorization Endpoint is the URL you registered during environment-specific configuration.

Don't validate the inbound URL — ignore unknown parameters

Your Authorization Endpoint MUST NOT reject the redirect based on the query parameters it receives. If a TPP appends additional parameters beyond client_id, response_type, and request_uri, simply ignore them — do not treat the redirect as malformed.

The authoritative authorization request is the signed Request JWT referenced by request_uri; the API Hub validates it when you call GET/auth in Step 5. Forward whatever you receive and let Headless Heimdall be the source of truth.

Step 5 — Call GET/auth

Upon receiving the end user redirect, your authorization server MUST immediately call GET/auth on the Headless Heimdall base URL, passing through all the query parameters received from the redirect.

typescript
const HH_BASE = process.env.HEADLESS_HEIMDALL_BASE_URL!
// e.g. 'https://hh.{lfiCode}.preprod.apihub.openfinance.ae'

// Pass through all query parameters received at your authorization endpoint
const queryString = new URLSearchParams({
  client_id: req.query.client_id,
  response_type: req.query.response_type,
  request_uri: req.query.request_uri,
}).toString()

const authResponse = await fetch(`${HH_BASE}/auth?${queryString}`, {
  method: 'GET',
  headers: {
    'Content-Type': 'application/json',
  },
  // mTLS with C3 transport certificate
  // agent: new https.Agent({ cert: c3TransportCert, key: c3TransportKey }),
})

if (authResponse.status === 303) {
  // Redirectable failure — redirect the end user to the URI in the Location header without modification
  return res.redirect(authResponse.headers.get('location'))
}

if (authResponse.status === 400) {
  // Non-redirectable failure — render an error page to the end user
  return res.status(400).render('auth-error')
}

const authData = await authResponse.json()

// Extract the interactionId and consentId — store both for subsequent calls
const interactionId = authData.interaction.interactionId
const consentId = authData.interaction.consentIdsList[0]

The 200 response contains:

FieldDescription
interaction.interactionIdUnique identifier for this authorization interaction — required for doConfirm and doFail
interaction.consentIdsListArray of consent IDs associated with this interaction (currently one element)
tppTPP details including clientId, tppName, orgId, and directory record
Handle all response codes

GET/auth can return three outcomes:

  • 200 — Success. Continue with the authorization journey.
  • 303 — Redirectable failure. The OIDC client was valid but the authorization request parameters failed validation. You MUST redirect the end user to the URI in the Location header without modification.
  • 400 — Non-redirectable failure. The OIDC client could not be verified. You MUST render an error page and MUST NOT redirect back to the TPP.

See the GET/auth API Reference for the full response schema.

Step 6 — Retrieve the consent details

Using the consentId from the GET/auth response, call GET/consents/{consentId} on the Consent Manager to retrieve the full consent object.

typescript
const CM_BASE = process.env.CONSENT_MANAGER_BASE_URL!
// e.g. 'https://cm.{lfiCode}.preprod.apihub.openfinance.ae'

const consentResponse = await fetch(`${CM_BASE}/consents/${consentId}`, {
  method: 'GET',
  headers: {
    'Content-Type': 'application/json',
  },
  // mTLS with C3 transport certificate
  // agent: new https.Agent({ cert: c3TransportCert, key: c3TransportKey }),
})

const consent = await consentResponse.json()

// The consent object contains the full consent details:
// - consent.data.consentBody.Data.Permissions (for data sharing)
// - consent.data.consentBody.Data.ExpirationDateTime
// - consent.data.consentBody.Data.PersonalIdentifiableInformation (if encrypted PII is present)
Encrypted PII

Some consent types include encrypted personally identifiable information (PII) — for example, debtor or creditor details on payment consents. If the consent contains encrypted PII fields, your LFI MUST decrypt the data and validate that the decrypted values align exactly with the Open Finance API specification. See Personal Identifiable Information for decryption and validation details.

See the GET/consents/{consentId} API Reference for the full response schema.

05 End user authentication

Step 7 — authenticate the end user

Your LFI MUST authenticate the end user using your standard authentication mechanisms. The authentication MUST satisfy the requirements defined in the Authentication Requirements.

07 Identifier requirements

psuIdentifiers and accountIds MUST be opaque

The values the LFI patches onto the consent — psuIdentifiers and accountIds — are stored centrally in the API Hub. They MUST be opaque, non-sensitive, LFI-defined references.

No sensitive values on the consent

The LFI MUST NOT use Emirates ID, passport number, name, email, phone number, IBAN, account number, card number, CIF, or any other value that on its own identifies a natural person or a real-world account.

See Consent Identifiers for the full rationale.

psuIdentifiers.userId

RuleRequirement
TypeString (required field on psuIdentifiers)
PatternLFI-defined opaque string. UUID (v4) recommended
UniquenessMUST uniquely identify a single end user within the LFI
StabilityMUST be the same value for the same end user across every consent they authorise — used by GET/psu/{userId}/consents
Sensitive valuesMUST NOT be an Emirates ID, email, phone, CIF, or any other PII

Additional custom fields on psuIdentifiers are permitted but MUST follow the same non-sensitive rule.

accountIds[]

RuleRequirement
TypeArray of strings, minItems: 1
Item formatString, 1–40 characters. UUID (v4) recommended
ValueMUST match the AccountId the LFI returns from its own /accounts APIs — the API Hub uses it to enrich downstream TPP requests
ImmutabilityOnce issued, the AccountId for an account MUST NOT change
UniquenessMUST uniquely identify a single account within the LFI
Sensitive valuesMUST NOT be an IBAN, account number, card number, or any externally-meaningful account identifier
CardinalityBank Service Initiation: exactly one (the debtor account). Bank Data Sharing: one or more selected accounts. Insurance Data Sharing: not patched directly — the Consent Manager mirrors insurancePolicyIds into accountIds via trigger