Bank Accounts
API Reference
API Introduction

API Introduction

Who calls these endpoints?External API (s2s)
Base path
/api/v1/bank-connect
Identity
Ed25519 request signature on every request — X-Key-Id, X-Date, X-Nonce, X-Signature. ±5 min clock window, single-use nonce. A Bearer token or a panel session is not accepted here (401 AUTH_HEADERS_MISSING).
Authorization
The key must carry the Bank Connections product (BANK_CONNECT) and the permission for the endpoints on this page: bank-connect:view · bank-connect:transactions:view · bank-connect:transfer. Without the product the call gets 403 PRODUCT_NOT_ENABLED; without the permission 403 API_INSUFFICIENT_SCOPE. See API Introduction › Permissions.
Cross-surface
These endpoints are the s2s surface — call them from your own server with a signed request. The same data has screens in the panel, but panel endpoints cannot be called with an API key, and these endpoints cannot be called with a panel session. Connecting and disconnecting a bank is done only in the panel.

The Bank Accounts API lets you read the accounts, balances and transactions of the banks you connected in the panel from your own server. If your connection supports it, the same API also sends money transfers from your connected account and exchanges currency between your accounts. Money movement endpoints need a separate permission and an IP allowlist; the read endpoints are not affected by them.

Endpoints

Base URL: https://api-gateway.lunixi.com/api/v1/bank-connect

MethodPathPermissionPage
GET/banksbank-connect:viewBanks
GET/connectionsbank-connect:viewConnections
GET/connections/{connectionId}bank-connect:viewConnections
GET/accountsbank-connect:viewAccounts
GET/accounts/{accountId}bank-connect:viewAccounts
POST/accounts/{accountId}/syncbank-connect:viewSync Now
GET/balancesbank-connect:transactions:viewBalances
GET/transactionsbank-connect:transactions:viewTransactions
GET/transactions/{transactionId}bank-connect:transactions:viewTransactions
GET/transfer-purposesbank-connect:transferMoney Transfers
POST/transfersbank-connect:transferMoney Transfers
GET/transfersbank-connect:transferMoney Transfers
GET/transfers/{transferId}bank-connect:transferMoney Transfers
GET/fx/ratesbank-connect:transferCurrency Exchange
POST/fx/quotesbank-connect:transferCurrency Exchange
POST/fx/ordersbank-connect:transferCurrency Exchange
GET/fx/ordersbank-connect:transferCurrency Exchange
GET/fx/orders/{orderId}bank-connect:transferCurrency Exchange

There is no endpoint for connecting or disconnecting a bank; that is done only in the panel. → Connect a Bank in the Panel

Authentication

These endpoints require a request signed with your API key on every request. An Authorization: Bearer token or a panel session is not accepted as identity; a request without the signature headers gets 401 AUTH_HEADERS_MISSING.

HeaderValue
X-Key-IdYour key ID — mk_test_… for test, mk_live_… for live
X-DateISO 8601 timestamp; within ±5 minutes of server time
X-NonceA unique value per request; single use
X-SignatureBase64 Ed25519 signature of the canonical string

The signed canonical string is the lines METHOD, full path, X-Date:… and X-Nonce:… joined with \n. Read endpoints have no body, so the Digest line is not written; signing money movement requests that carry a body: Money Transfers › Signing a request with a body. Canonical string rules and key management: Authentication › Canonical string.

⚠️

The signed path includes the query string and must match the path you send exactly: when signing GET /api/v1/bank-connect/transactions?limit=50&direction=CREDIT, the second line is /api/v1/bank-connect/transactions?limit=50&direction=CREDIT.

The cURL examples in this section compute the signature with the shell function below (requires OpenSSL 3.0 or later):

# lunixi-sign.sh — Usage: source lunixi-sign.sh; lunixi_sign GET "/api/v1/bank-connect/accounts"
LUNIXI_KEY_ID="mk_test_..."
LUNIXI_KEY_FILE="lunixi-signing.key"
 
lunixi_sign() {
  local method="$1" req_path="$2"
  DATE=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
  NONCE=$(openssl rand -hex 16)
  printf '%s\n%s\nX-Date:%s\nX-Nonce:%s' "$method" "$req_path" "$DATE" "$NONCE" > canonical.txt
  SIGNATURE=$(openssl pkeyutl -sign -inkey "$LUNIXI_KEY_FILE" -rawin -in canonical.txt | base64 | tr -d '\n')
}

PHP SDK

The PHP examples use the bankConnect() client of the PHP SDK (lunixi/php-sdk, 0.14.0 or later; 0.22.0 or later for transfers and currency exchange). The SDK signs every request for you:

use Lunixi\Sdk\LunixiClient;
 
$lunixi = LunixiClient::create([
    'baseUrl'     => 'https://api-gateway.lunixi.com',
    'keyId'       => getenv('LUNIXI_KEY_ID'),
    'privateKey'  => getenv('LUNIXI_PRIVATE_KEY_PEM'),
    'environment' => 'TEST', // your key's environment: TEST or LIVE
]);
 
$bankConnect = $lunixi->bankConnect();
  • The list methods (listBanks, listConnections, listAccounts, listBalances, listTransactions) return a single page: items(), hasMore(), nextCursor(). The matching iterate… methods walk all pages for you.
  • The SDK rejects unknown filters, values outside their set and date or amount ranges that break the rules with Lunixi\Sdk\Exception\ConfigurationException, before sending the request.
  • API errors arrive as Lunixi\Sdk\Exception\ApiException; branch on getStatusCode() and getErrorCode().

Permissions

After the signature is verified, two things are checked:

  1. Product. Your key must carry the Bank Connections product (BANK_CONNECT); otherwise 403 PRODUCT_NOT_ENABLED. → Error Handling › PRODUCT_NOT_ENABLED
  2. Permission. Your key must carry the endpoint's permission; otherwise 403 API_INSUFFICIENT_SCOPE.
PermissionEndpoints
bank-connect:viewBanks, connections, accounts (masked, without balances) and sync now
bank-connect:transactions:viewBalances, transaction list and transaction detail
bank-connect:transferMoney transfer and currency exchange endpoints (reads included). Given only to a key with an IP allowlist.
⚠️

bank-connect:view is not enough to read balances and transactions. Balances and transactions are sensitive data and have their own permission. Your existing keys do not get these permissions automatically; add the ones you need to your API key's permissions in the panel, or create a new key for this purpose.

IP allowlist

Money movement endpoints (bank-connect:transfer) have no verification code; the approval is the key's permission, the request signature and the IP address the request comes from. Therefore:

  1. When you create or update a key: the bank-connect:transfer permission can only be given to a key with an IP allowlist. Adding it while the list is empty is refused with 400 organization.api_key.scope_requires_ip_allowlist.
  2. On every request: the request's IP address must be on the key's list. A key with an empty list gets 403 bank_connect.auth.ip_allowlist_required; an address not on the list gets 403 bank_connect.auth.ip_not_allowed. If the list cannot be verified for a moment, the request gets 503 bank_connect.auth.ip_allowlist_unavailable; retry the same request.

The list takes single IP addresses (IPv4 or IPv6); address ranges (CIDR) are not accepted, and up to 50 addresses can be added. Changes to the list take effect within 15 seconds. Read endpoints (bank-connect:view, bank-connect:transactions:view) are not subject to this rule.

Put your server's outbound IP address on the list. If a load balancer, NAT or a new server changes the outbound address, update the list. → Authentication › IP allowlist

Environment

Your key's environment (test or live) determines the environment. A test key sees only connections and data in the test environment, a live key only those in the live environment. You do not send environment in the query; if you do, the request gets 400 VALIDATION_ERROR. A record of another organization or of the other environment returns 404.

Response envelope

On success, the data is under data. For endpoints that return a single record, data is the record itself. For list endpoints, records are in data.items and the pagination info pageInfo sits next to data:

{
  "status": "success",
  "code": "SUCCESS",
  "data": {
    "items": [ ]
  },
  "pageInfo": { "hasMore": false, "nextCursor": null, "totalCount": null },
  "statusCode": 200,
  "requestId": "00000000-0000-0000-0000-000000000000"
}

Envelope fields omitted from the examples for brevity (message, timestamp …) are also present in the response; for the full field list and the error envelope see Error Handling. On errors, branch on code, not on message. This product's codes: Error Codes.

Common field rules

RuleDetail
AmountsInteger in minor units (kuruş): "amountMinor": 150000 = ₺1,500.00. No decimals.
CurrencyISO 4217, e.g. TRY.
TimesISO 8601. Responses are in UTC (2026-01-01T07:00:00.000Z); send values with a time zone in query filters (2026-01-01T10:00:00+03:00).
No value, no fieldAn empty or unknown value is not returned as null or an empty string; the field is absent from the response. Write your code so a field may be missing.
IdentifiersbankId, connectionId, accountId, transactionId are UUIDs. A malformed ID returns 404.
MaskingIBAN, account number and counterparty details are returned masked only. → Concepts › Masking
Unknown parameterAn undeclared query parameter or body field returns 400 VALIDATION_ERROR.
Money movement responsesIn transfer, exchange quote and exchange order responses, a field without a value is null; the field is present.

Pagination

List endpoints use cursor-based pagination:

ParameterDescription
limitRecords per page, 1–100. Default 25. Above 100 returns 400.
cursorpageInfo.nextCursor from the previous response. Not sent on the first page.
  • If pageInfo.hasMore is true, request the next page with cursor=<nextCursor>.
  • On these endpoints pageInfo.totalCount is always null; even with includeTotal=true no total is returned. hasMore is enough to move forward.
  • Do not build or alter cursors. A malformed cursor returns 400 bank_connect.cursor.invalid; start again from the first page.
  • Reset the cursor when you change filters; a cursor from one set of filters is not used with other filters.

Next steps