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.

  1. Generate an access token for the request, this will return a JSON Web Token (JWT)
  2. 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
  3. 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",
}
ParameterDescriptionType
access_tokenThe access token value for use with the Lean APIsString
token_typeWill always be bearer, indicates the type of token returnedString
expires_inThe time in seconds until the access token expiresInteger
scopeThe scope of the access token and what resources it can accessString

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_id has a unique constraint, but does not need to map directly to the id of the user in your database, for example you could pass prod_usr_1246 as the app_user_id, so that you can identify that the Customer is a user of your production database. In this case, you should save both the customer_id and app_user_id against 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_id represent the Lean customer identifier via /customers/v1 API or the app_user_id that represents your unique identifier for the customer.

📘

Please note: app_user_id has a unique constraint, but does not need to map directly to the id of the user in your database, for example you could pass prod_usr_1246 as the app_user_id, so that you can identify that the Customer is a user of your production database. In this case, you should save both the customer_id and app_user_id against 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:

ParameterInclude in StatusDescription
customer_id or app_user_idSuccess & FailureUUID 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_identifierSuccess & FailureUnique code for the bank/financial institution (e.g. ALINMA_SAU)
entity_idSuccessUUID identifying connected bank account for the customer
granular_status_codeFailureEnum name categorizing the failure reason. Key values for OB: CONSENT_REJECTED (user denied), BANK_ISSUE or UNSEEN_BANK_ERROR (bank technical error).
status_additional_infoFailureHuman-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-b3490768ad95
https://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

LeanKSA Open Banking - 2022.11.01-final-errata2
accountsReadAccountsBasic, ReadAccountsDetail
balanceReadBalances
transactionsReadTransactionsDetail ReadTransactionsBasic ReadTransactionsCredits ReadTransactionsDebits
identityReadParty ReadPartyPSU ReadPartyPSUIdentity
identitiesReadParty ReadPartyPSU ReadPartyPSUIdentity
beneficiariesReadBeneficiariesBasic ReadBeneficiariesDetail
scheduled_paymentsReadScheduledPaymentsBasic ReadScheduledPaymentsDetail
standing_ordersReadStandingOrdersBasic ReadStandingOrdersDetail
direct_debitsReadDirectDebits

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
});
How to integrate

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.