Lean Open Banking Integration Guide
A step by step introduction to building with Lean Open Banking APIs in the Kingdom of Saudi Arabia.
Lean Environments
- Sandbox: to get started with test credentials and mock bank users and data.
- Production to launch your app with live credentials and connecting with real banks.
- Lean Dashboard
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' Generating Customer Access Tokens for LinkSDK Flow
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=customer.<customer_id>'| Parameter | Description | Type |
|---|---|---|
| client_id | Your application ID - this can be retrieved from your Application Dashboard account. | String (UUID) |
| client_secret | Your client secret - this can be retrieved once from your Application Dashboard - subsequent retrievals will invalidate your existing client secret. | String |
| grant_type | The type of access you require a token for - currently this should always be set toclient_credentials | String |
| scope | What the scope of the access token should be, either api or customer.<customer_id> | String |
How to get client_id & client_secret
- Sign up to Lean Dashboard
- Go to Integration tab and find both paramters for your apllication as shown below:

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 |
More details about authentication here.
Step 2: Create Customer
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: Link SDK Consent Journey
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, React Native and Flutter.
Check it out here
Live demo of Lean LinkSDK
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.
Required fields
| Field | Type | Description |
|---|---|---|
app_token | string | Your Lean application token from the Lean dashboard. |
customer_id | string | Unique identifier of the end customer in your system. |
permissions | string[] | Permissions to request. Must contain at least one value. "accounts", "identity", "balance","transactions", "beneficiaries", "identities", "scheduled_payments", "direct_debits", "standing_orders" |
access_token | string | A valid Lean JWT with customer scope. When provided, the SDK skips its own auth step and uses this token as the Authorization: Bearer header on every API request. The JWT must include an exp claim with at least 10 minutes of remaining validity. |
success_redirect_url | string | URL to redirect to on successful completion. |
fail_redirect_url | string | URL to redirect to on failure or cancellation. |
show_consent_explanation | boolean | To set as True to show an explanation screen before the customer grants consent. |
Lean.connect({
app_token: APP_TOKEN,
access_token: CUSTOMER_ACCESS_TOKEN,
customer_id: CUSTOMER_ID,
permissions: [
"identity",
"accounts",
"balance",
"transactions",
"beneficiaries",
],
sandbox: true,
show_consent_explanation: 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
});
Check it out here
Live demo of Lean LinkSDK
Step 4: Webhooks
Webhooks are used to immediately notify your backend of events that take place in the Lean ecosystem. These are especially useful for events that take place on your front end through the Lean SDK or events that take place in data workflow like data refreshes . Once you receive an event on your server, you can process and act on it as you need.
More details about webhooks here including IP whitelisting and retry policy.
The following are the main webhooks for the connecting the bank accounts of your users:
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.
{
"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": "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 webhooks notify the progress of fetching data for a connected entity: where a final entity.data.refresh.updated webhook with status FINISHED webhook is sent once all data types have been completed and data is available to retrieve in Lean store via API calls.
More details about data workflow here.
{
"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.
{
"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": "PERSONAL",
"main_color": "#1beb75",
"background_color": "#ffffff"
}
}
}Step 5: Verify Entity Ownership (Recommended)
API Reference
Upon receiving entity.data.refresh.updated webhook with status FINISHED , you can start making an API call to verify entity ownership of the end-user of the connect bank account against bank records for multiple IBANs, you can use the verify entity ownership API:
Request
curl --location '/verifications/v2/entity' \
--header 'Content-Type: application/json' \
--header 'Accept: application/json' \
--data '{
"entity_id": "cdd274fc-2195-4058-a929-ec10b7470694",
"identifications": [
{
"type": "NATIONAL_ID",
"value": "1106972886"
}
]
}'Response
Theentity_ownership_verified=true represents that passed national ID as the verified account owner.
{
"status": "OK",
"results_id": "d4d564ab-e86b-41c4-ac9c-bf3281347ac6",
"message": "Data successfully retrieved",
"meta": null,
"timestamp": "2026-05-10T13:14:48.238271027Z",
"status_detail": null,
"verifications": {
"entity_ownership_verified": true,
"verification_method": "OPEN_BANKING",
"ibans": [
{
"iban": "SA0236DZJWMCRI65147ERH2V",
"iban_ownership_verified": true
},
{
"iban": "SA04203XGFV6Y7UWB7IYNK0I",
"iban_ownership_verified": true
}
]
}
}Step 6: Fetch Account Information Data APIs
The table lists all available open banking APIs and their API references
Once the customer is connected to his bank, you will be able to query a list of Financial Data & Insights APIs, analyze & utilize them accordingly:
Check it out here
Lean Postman Collections
Raw data APIs | Verification & Data enrichment APIs |
|---|---|
| Get Accounts | |
| Get current user's identity | Verify Entity Ownership |
| Get Balances | Get Income |
| Get Transactions | Get Expenses (Retail only) |
| Get Beneficiaries | Get Cashflow |
| Get Direct Debits | Get Cash Balances |
| Get Identities | |
| Get Scheduled Payments | |
| Get Standing Orders |
Wait for the
entity.data.refresh.updatedwebhook with statusFINISHEDbefore making the API calls to to receive sync results and avoid API timeouts.
Making your first data API call
With an Entity set up, the first API to call will normally be the data/v2/accounts endpoint. This simple GET request returns a list of the available accounts to query.
Let's look at a simple function to call the Accounts API:
curl --request GET \
--url 'https://sandbox.sa.leantech.me/data/v2/accounts?entity_id=550e8400-e29b-41d4-a716-446655440000' \
--header 'accept: application/json' \
--header 'Authorization: Bearer API_ACCESS_TOKEN'Step 7: Fetch Entities for a Customer
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
}
]
}
]
}
]Step 8: Fetch Available Bank Providers
To 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 if that's required, otherwise the LinkSDK handles showing the banks list.
- 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,
"max": 100000
}
],
"international_transfer_limits": [],
"international_destinations": [],
"account_type": "PERSONAL",
"connection_type": "OPEN_BANKING",
"availability": {
"active": {
"payments": false,
"data": false
},
"enabled": {
"payments": true,
"data": true
}
}
}
]How disablement is represented
Each bank object contains an availability object as shown in the above response:
"availability": {
"active": {
"data": false
},
"enabled": {
"data": true
}
}availability.active→ set by Lean when a bank is disabled at the platform levelavailability.enabled→ set by clients when a bank is disabled in the Developer Portal
Both must be true for a bank to be considered available. If either active or enabled is false, the bank should not be displayed.
Updated 7 days ago
