Lean Open Banking Account Access Information Integration Guideline
Step 1: Authenticating with OAuth
OAuth is a standard authentication method used to secure APIs and other infrastructure using Public and Private secrets combined with short-lived access tokens. In order to get started with OAuth authentication you will need the following:
- An Application Dashboard account with Admin or Developer role access.
- Your Application ID and Client Secret (found under the 'Integration' tab in the Application Dashboard).
- An application with access to OAuth as an authentication method.
Scopes & The OAuth flow
OAuth is implemented to secure two channels of access to Lean. Access from your backend to Lean's APIs with scope=api, and access for your customers to the Lean Link SDK with scope=customer.<customer_id>.
In both cases the flow for creating, editing or modifying resources on the Lean platform is the same.
- Generate an access token for the request, this will return a JSON Web Token (JWT)
- Use the JWT as a Bearer token in subsequent API calls, or as an authentication for the Link SDK method call you want to make
- Tokens must be generated in your backend to avoid using the client secret in your frontend since it's vulnerable
Generating Access Tokens for Backend API Calls
curl -X POST 'https://auth.sa.leantech.me/oauth2/token' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode 'client_id=<LEAN_APPLICATION_ID>' \
--data-urlencode 'client_secret=<LEAN_CLIENT_SECRET>' \
--data-urlencode 'grant_type=client_credentials' \
--data-urlencode 'scope=api'Response
{
"access_token": "YOUR_ENCODED_JWT",
"token_type": "bearer",
"expires_in": 3599,
"scope": "api",
}
| Parameter | Description | Type |
|---|---|---|
| access_token | The access token value for use with the Lean APIs | String |
| token_type | Will always be bearer, indicates the type of token returned | String |
| expires_in | The time in seconds until the access token expires | Integer |
| scope | The scope of the access token and what resources it can access | String |
Step 2: Create Customer
Overview
In order to start the consent journey and fetching data, you first need to create a Customer resource. This creates a relationship between your application, user and all the services they consume within Lean into a single object.
To create a Customer, call the /customers/v1 endpoint with a reference to your app_user_id.
curl -X POST 'https://sandbox.sa.leantech.me/customers/v1/' \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer <backend_api_access_token>' \
--data-raw '{
"app_user_id": "YOUR_IDENTIFIER_FOR_CUSTOMER"
}'Please note:
app_user_idhas a unique constraint, but does not need to map directly to the id of the user in your database, for example you could passprod_usr_1246as theapp_user_id, so that you can identify that the Customer is a user of your production database. In this case, you should save both thecustomer_idandapp_user_idagainst your user table for later retrieval and mapping.
As part of the response you will receive the Lean customer_id which you should save against your user table for future reference.
{
"app_user_id": "IDENTIFIER_FOR_CUSTOMER",
"customer_id": "f08fb010-878f-407a-9ac2-a7840fb56185"
}Step 3: Fetch Available Bank Providers
Retrieve the list of banks supported by Lean with their availability details.
Additional details:
- More details and how to create your own bank list here.
- Details about availability and bank disablement here.
Request:
curl --request GET \
--url 'https://sandbox.sa.leantech.me/banks/v1/?account_types=PERSONAL' \
--header 'accept: application/json' \
--header 'authorization: Bearer api_access_token'Response:
[
{
"identifier": "LEA2_SAMAOB_SAU",
"name": "Mockbank 2",
"arabic_name": null,
"commercial_name": "Mockbank 2",
"commercial_name_arabic": null,
"logo": "https://cdn.leantech.me/img/banks/color-gmockbank2.png",
"logo_alt": "https://cdn.leantech.me/img/banks/color-gmockbank2.png",
"main_color": "#1beb75",
"background_color": "#ffffff",
"theme": "light",
"country_code": "SAU",
"active": true,
"mock": true,
"traits": [
"auth-redirect",
"decoupled-auth-redirect"
],
"supported_account_types": [
"CREDIT",
"SAVINGS",
"CURRENT"
],
"supported_account_sub_types": [
"CURRENT",
"SAVINGS",
"CREDIT"
],
"bank_type": "RETAIL",
"swift_code": "MOCKSA03",
"transfer_limits": [],
"international_transfer_limits": [],
"international_destinations": [],
"account_type": "PERSONAL",
"connection_type": "OPEN_BANKING",
"availability": {
"active": {
"payments": false,
"data": false
},
"enabled": {
"payments": true,
"data": true
}
}
},
{
"identifier": "LEA1_SAMAOB_SAU",
"name": "Mockbank 1",
"arabic_name": null,
"commercial_name": "Mockbank 1",
"commercial_name_arabic": null,
"logo": "https://cdn.leantech.me/img/banks/color-gmockbank2.png",
"logo_alt": "https://cdn.leantech.me/img/banks/color-gmockbank2.png",
"main_color": "#1beb75",
"background_color": "#ffffff",
"theme": "light",
"country_code": "SAU",
"active": true,
"mock": true,
"traits": [
"auth-redirect",
"decoupled-auth-redirect"
],
"supported_account_types": [
"CREDIT",
"SAVINGS",
"CURRENT"
],
"supported_account_sub_types": [
"CURRENT",
"SAVINGS",
"CREDIT"
],
"bank_type": "RETAIL",
"swift_code": "MOCKSA01",
"transfer_limits": [
{
"currency": "SAR",
"min": 10.000,
"max": 100000.000
}
],
"international_transfer_limits": [],
"international_destinations": [],
"account_type": "PERSONAL",
"connection_type": "OPEN_BANKING",
"availability": {
"active": {
"payments": false,
"data": false
},
"enabled": {
"payments": true,
"data": true
}
}
}
]Step 4: Consent Journey
Option 1: Create Connect Link V2 API
This API allows you to create a consent URL which enables the end-user to start a consent journey and complete it where the users authorizes access to their bank account information on the bank app/website.
This API should be called using a API access token.
The API accepts
customer_idrepresent the Lean customer identifier via/customers/v1API or theapp_user_idthat represents your unique identifier for the customer.
Please note:
app_user_idhas a unique constraint, but does not need to map directly to the id of the user in your database, for example you could passprod_usr_1246as theapp_user_id, so that you can identify that the Customer is a user of your production database. In this case, you should save both thecustomer_idandapp_user_idagainst your user table for later retrieval and mapping.
Request
curl --request POST \
--url https://sandbox.sa.leantech.me/v2/accounts-access-consents \
--header 'accept: application/json' \
--header 'content-type: application/json' \
--data '
{
"app_user_id": "YOUR_IDENTIFIER_FOR_CUSTOMER",
"bank_identifier": "ALINMA_SAU",
"success_redirect_url": "https://www.leantech.me/?success=true",
"failure_redirect_url": "https://www.leantech.me/?success=false",
"permissions": [
"accounts",
"identity",
"balance",
"transactions",
"identities",
"scheduled_payments",
"standing_orders",
"direct_debits",
"beneficiaries"
],
"expiration_date_time": "2026-04-16T01:10:53+03:00",
"transaction_from_date_time": "2025-04-16T01:29:56.000Z"
}
'Response
The redirect_url URL that must be used to allow the user to complete the consent authorization flow.
{
"redirect_url": "https://m.alinma.com/landing/auth?request_uri=urn:ietf:params:oauth:request_uri:1b859eb3-54e1-48b2-b067-6fca73682162&response_type=code id_token&client_id=422c1444-8d98-4d81-b7cb-5120d21101f4&scope=openid accounts:urn:INMASARI:kac-30e0f1f2-ba31-49e4-9dab-91b62586e56f&redirect_uri=https://obksabank.leantech.me/data/callback/ALINMA_SAU",
"bank_consent_id": "urn:INMASARI:kac-30e0f1f2-ba31-49e4-9dab-91b62586e56f",
"customer_id": "7affbbf9-be53-433c-a598-c06449c2b4b0",
"app_user_id": "YOUR_IDENTIFIER_FOR_CUSTOMER"
}Once the account linking is completed Lean will make a callback to the success_redirect_url or failure_redirect_url based on the status of consent authorisation, and the redirect url will include the following query parameters:
| Parameter | Include in Status | Description |
|---|---|---|
customer_id or app_user_id | Success & Failure | UUID of the Lean customer who authorized the consent, same as customer_id via create customer API or your customer identifier passed via app_user_id. |
bank_identifier | Success & Failure | Unique code for the bank/financial institution (e.g. ALINMA_SAU) |
entity_id | Success | UUID identifying connected bank account for the customer |
granular_status_code | Failure | Enum name categorizing the failure reason. Key values for OB: CONSENT_REJECTED (user denied), BANK_ISSUE or UNSEEN_BANK_ERROR (bank technical error). |
| status_additional_info | Failure | Human-readable message describing the failure |
consent_attempt_id |
https://www.leantech.me/?success=true&entity_id=00e6976a-1a54-484b-a699-2a4dadfebcb5&bank_identifier=ALINMA_SAU&customer_id=7affbbf9-be53-433c-a598-c06449c2b4b0&consent_attempt_id=6bddaf84-d820-438e-b561-b3490768ad95https://www.leantech.me/?success=false&bank_identifier=ALINMA_SAU&customer_id=7affbbf9-be53-433c-a598-c06449c2b4b0&granular_status_code=CONSENT_REJECTED&status_additional_info=The%20consent%20request%20was%20rejected%20by%20the%20customer.Permissions Mapping
| Lean | KSA Open Banking - 2022.11.01-final-errata2 |
|---|---|
| accounts | ReadAccountsBasic, ReadAccountsDetail |
| balance | ReadBalances |
| transactions | ReadTransactionsDetail ReadTransactionsBasic ReadTransactionsCredits ReadTransactionsDebits |
| identity | ReadParty ReadPartyPSU ReadPartyPSUIdentity |
| identities | ReadParty ReadPartyPSU ReadPartyPSUIdentity |
| beneficiaries | ReadBeneficiariesBasic ReadBeneficiariesDetail |
| scheduled_payments | ReadScheduledPaymentsBasic ReadScheduledPaymentsDetail |
| standing_orders | ReadStandingOrdersBasic ReadStandingOrdersDetail |
| direct_debits | ReadDirectDebits |
Option 2: Using Lean Link SDK (recommended)
Overview
Lean Link SDK enables you to embed the user bank connections flow directly into their applications. It's available on platforms including Web, iOS, Android, and Flutter.
The connect method is used to connect a customer to the Data API. You can use this method to generate an Entity with a single customer login.
Lean.connect({
app_token: "YOUR_APP_TOKEN",
access_token:customer_access_token,
customer_id: customer_id,
permissions: [
"identity","accounts","balance","transactions", "identities",
"scheduled_payments", "standing_orders", "direct_debits", "beneficiaries"
],
sandbox: true,
success_redirect_url: "https://www.leantech.me/?success=true",
fail_redirect_url: "https://www.leantech.me/?success=false",
access_from: "2018-11-01T00:00:00+4:00", //optional
access_to: "2022-11-01T00:00:00+4:00" //optional
});Step 5: Webhooks (recommended)
Entity created
"type": "entity.created"
This webhook is triggered when your customer successfully connects their account with the bank. The entity object is used by you to retrieve data from your customer's bank account. Where the entity_id serves as a token representing the holding bank account.
Example
{
"type": "entity.created",
"message": "An entity object has been created.",
"payload": {
"id": "dd64bba9-9446-4ca0-b9ed-bec2b2a49024",
"app_user_id": "lean_test_framework_user_1678954843328",
"customer_id": "a8e9bf82-245a-4f5c-b048-9024fd7910cb",
"permissions": [
"transactions", "identity", "identities", "accounts",
"standing_orders", "direct_debits", "scheduled_payments", "beneficiaries"
],
"bank_details": {
"logo": "https://cdn.leantech.me/img/banks/white-lean.png",
"name": "Lean SAMA Open Banking MockBank",
"account_type": "BUSINESS" | "PERSONAL",
"identifier": "LEA1_SAMAOB_SAU",
"main_color": "#1beb75",
"background_color": "#ffffff"
}
}
}Entity Data Refresh Updated
"type": "entity.data.refresh.updated"
The entity.data.refresh.updated webhook notifies the progress of population of data for the connected entity where a final entity.data.refresh.updated webhook with status FINISHED webhook is sent once all requests have been completed and data is available in Lean store.
More details about data workflow here.
Example
{
"type": "entity.data.refresh.updated",
"message": "An entity data refresh state has been updated.",
"payload": {
"refresh_id": "d4718195-fef6-43ff-a3aa-69fc257752ab",
"entity_id": "d4718195-fef6-43ff-a3aa-69fc257752ab",
"app_user_id": "consent_window_24",
"customer_id": "d4718195-fef6-43ff-a3aa-69fc257752ab",
"status": "PENDING/FINISHED",
"data_status": {
"accounts": "PENDING/SUCCESS/FAILED/UNSUPPORTED",
"identity": "PENDING/SUCCESS/FAILED/UNSUPPORTED",
"account_data": [
{
"account_id": "d4718195-fef6-43ff-a3aa-69fc257752ab",
"balance": "PENDING/SUCCESS/FAILED/UNSUPPORTED",
"identity": "PENDING/SUCCESS/FAILED/UNSUPPORTED",
"transactions": "PENDING/PARTIAL/SUCCESS/FAILED/UNSUPPORTED",
"scheduled_payments": "PENDING/PARTIAL/SUCCESS/FAILED/UNSUPPORTED",
"direct_debits": "PENDING/PARTIAL/SUCCESS/FAILED/UNSUPPORTED",
"standing_orders": "PENDING/PARTIAL/SUCCESS/FAILED/UNSUPPORTED",
"beneficiaries": "PENDING/SUCCESS/FAILED/UNSUPPORTED",
"transaction_availability": {
"start": "<DateTime>",
"end": "<DateTime>",
"complete_months": 12
}
},{
"account_id": "b5098d49-840d-459e-9ea1-d02901af9b8c",
"balance": "PENDING/SUCCESS/FAILED/UNSUPPORTED",
"identity": "PENDING/SUCCESS/FAILED/UNSUPPORTED",
"transactions": "PENDING/PARTIAL/SUCCESS/FAILED/UNSUPPORTED",
"scheduled_payments": "PENDING/PARTIAL/SUCCESS/FAILED/UNSUPPORTED",
"direct_debits": "PENDING/PARTIAL/SUCCESS/FAILED/UNSUPPORTED",
"standing_orders": "PENDING/PARTIAL/SUCCESS/FAILED/UNSUPPORTED",
"beneficiaries": "PENDING/SUCCESS/FAILED/UNSUPPORTED",
"transaction_availability": {
"start": "<DateTime>",
"end": "<DateTime>",
"complete_months": 12
}
}
]
}
}
}Entity updated
"type": "entity.updated"
This webhook is triggered when there is a change in the consents for the entity. This happens when your customer re-connect again the same bank account again. For the first time, you will receive an entity.created webhook mentioned above.
Example
{
"type": "entity.updated",
"message": "An entity object has been updated.",
"payload": {
"id": "d4718195-fef6-43ff-a3aa-69fc257752ab",
"app_user_id": "lean_test_framework_user_1678955197525",
"customer_id": "22df5cab-1002-4835-bc78-5d1854b3fc69",
"permissions": [
"transactions", "identity", "identities", "accounts",
"standing_orders", "direct_debits", "scheduled_payments", "beneficiaries"
],
"bank_details": {
"logo": "https://cdn.leantech.me/img/banks/white-lean.png",
"name": "Lean SAMA Open Banking MockBank",
"identifier": "LEA1_SAMAOB_SAU",
"account_type": "BUSINESS" | "PERSONAL",
"main_color": "#1beb75",
"background_color": "#ffffff"
}
}
}More details about webhooks here.
Step 6: Fetch Account Information Data APIs
The table lists all available open banking APIs and their API references
Step 7: Fetch Consents & Entities for a Customer
Get list of consents for a customer
Get all consents for a customer along with the consent details.
curl --request GET \
--url 'https://sandbox.sa.leantech.me/customers/v1/{customer_id}/consents?pageNumber=0&pageSize=20'
--header 'accept: application/json'{
"data": [
{
"id": "f3b7a8a2-7e9b-4d2a-9b80-9f0fbd4f1c11",
"bank_consent_id": "urn:INMASARI:kac-30e0f1f2-ba31-49e4-9dab-91b62586e56f",
"consent_status": "ACTIVE",
"standard_consent_status": "AUTHORISED",
"consent_type": "ACCOUNT_ACCESS",
"permissions": {
"identity": true,
"accounts": true,
"balance": true,
"transactions": true,
"identities": false,
"scheduled_payments": false,
"standing_orders": false,
"direct_debits": false,
"beneficiaries": false
},
"creation_date_time": "2026-04-16T01:10:53+03:00",
"status_update_date_time": "2026-04-20T09:15:00+03:00",
"transaction_from_date_time": "2025-04-16T01:29:56.000Z",
"transaction_to_date_time": "2026-04-16T01:29:56.000Z",
"expiration_date_time": "2027-04-16T01:10:53+03:00",
"accounts": [
{
"scheme_name": "IBAN",
"identification": "SA0380000000608010167519",
"secondary_identification": "001",
"name": "John Doe"
}
],
"bank_identifier": "ALINMA_SAU"
}
],
"page": {
"number": 0,
"size": 20,
"total_elements": 1,
"total_pages": 1
}
}Get Consent Details
Returns the details of a single consent for the given customer. The consent must belong to of the customer.
curl --request GET \
--url https://sandbox.sa.leantech.me/customers/v1/customer_id/consents/consent_id \
--header 'accept: application/json'{
"id": "f3b7a8a2-7e9b-4d2a-9b80-9f0fbd4f1c11",
"bank_consent_id": "urn:INMASARI:kac-30e0f1f2-ba31-49e4-9dab-91b62586e56f",
"consent_status": "ACTIVE",
"standard_consent_status": "AUTHORISED",
"consent_type": "ACCOUNT_ACCESS",
"permissions": {
"identity": true,
"accounts": true,
"balance": true,
"transactions": true,
"identities": false,
"scheduled_payments": false,
"standing_orders": false,
"direct_debits": false,
"beneficiaries": false
},
"creation_date_time": "2026-04-16T01:10:53+03:00",
"status_update_date_time": "2026-04-20T09:15:00+03:00",
"transaction_from_date_time": "2025-04-16T01:29:56.000Z",
"transaction_to_date_time": "2026-04-16T01:29:56.000Z",
"expiration_date_time": "2027-04-16T01:10:53+03:00",
"accounts": [
{
"scheme_name": "IBAN",
"identification": "SA0380000000608010167519",
"secondary_identification": "001",
"name": "John Doe"
}
],
"bank_identifier": "ALINMA_SAU"
}Get Entities for a customer
An Entity is generated whenever a customer connects a new bank account that is associated with a list of consents:
curl --request GET \
--url https://sandbox.sa.leantech.me/customers/v1/{customer_id}/entities \
--header 'accept: application/json' \
--header 'authorization: Bearer api_access_token'[
{
"id": "22730193-2a2f-4a14-a982-b185acc841d4",
"customer_id": "6005b400-8a6e-460b-9d98-6c9a9a72531b",
"bank_identifier": "LEA1_SAMAOB_SAU",
"permissions": {
"identity": true,
"accounts": true,
"balance": true,
"transactions": true,
"identities": true,
"scheduled_payments": false,
"standing_orders": false,
"direct_debits": false,
"beneficiaries": false
},
"bank_type": "RETAIL",
"created_at": "2026-04-13T13:34:37.559481Z",
"consents": [
{
"id": "8e50a1d6-53dd-4182-ba76-627322b1d7bc",
"bank_consent_id": "147352",
"bank_identifier": null,
"consent_type": null,
"consent_status": "ACTIVE",
"standard_consent_status": null,
"permissions": {
"identity": true,
"accounts": true,
"balance": true,
"transactions": true,
"identities": true,
"scheduled_payments": false,
"standing_orders": false,
"direct_debits": false,
"beneficiaries": false
},
"creation_date_time": "2026-04-13T13:34:11.819965Z",
"status_update_date_time": "2026-04-13T13:34:11.819965Z",
"expiration_date_time": "2026-04-14T13:34:11.499Z",
"transaction_from_date_time": "2025-04-10T20:00:00.601Z",
"transaction_to_date_time": "2026-04-14T13:34:11.499Z",
"accounts": [
{
"scheme_name": "IBAN",
"identification": "SA04203XGFV6Y7UWB7IYNK0I",
"name": "Mae Zulauf Current Account",
"secondary_identification": null
},
{
"scheme_name": "ACCOUNT_NUMBER",
"identification": "3XGFV6Y7UWB7IYNK0I",
"name": "Mae Zulauf Current Account",
"secondary_identification": null
}
]
}
]
}
]If you need to allow your customers to revoke the consent they have granted you. For this, you can use our Consent deletion API.
Updated 7 days ago
