API Introduction
- 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 gets403 PRODUCT_NOT_ENABLED; without the permission403 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
| Method | Path | Permission | Page |
|---|---|---|---|
GET | /banks | bank-connect:view | Banks |
GET | /connections | bank-connect:view | Connections |
GET | /connections/{connectionId} | bank-connect:view | Connections |
GET | /accounts | bank-connect:view | Accounts |
GET | /accounts/{accountId} | bank-connect:view | Accounts |
POST | /accounts/{accountId}/sync | bank-connect:view | Sync Now |
GET | /balances | bank-connect:transactions:view | Balances |
GET | /transactions | bank-connect:transactions:view | Transactions |
GET | /transactions/{transactionId} | bank-connect:transactions:view | Transactions |
GET | /transfer-purposes | bank-connect:transfer | Money Transfers |
POST | /transfers | bank-connect:transfer | Money Transfers |
GET | /transfers | bank-connect:transfer | Money Transfers |
GET | /transfers/{transferId} | bank-connect:transfer | Money Transfers |
GET | /fx/rates | bank-connect:transfer | Currency Exchange |
POST | /fx/quotes | bank-connect:transfer | Currency Exchange |
POST | /fx/orders | bank-connect:transfer | Currency Exchange |
GET | /fx/orders | bank-connect:transfer | Currency Exchange |
GET | /fx/orders/{orderId} | bank-connect:transfer | Currency 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.
| Header | Value |
|---|---|
X-Key-Id | Your key ID — mk_test_… for test, mk_live_… for live |
X-Date | ISO 8601 timestamp; within ±5 minutes of server time |
X-Nonce | A unique value per request; single use |
X-Signature | Base64 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 matchingiterate…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 ongetStatusCode()andgetErrorCode().
Permissions
After the signature is verified, two things are checked:
- Product. Your key must carry the Bank Connections product (
BANK_CONNECT); otherwise403 PRODUCT_NOT_ENABLED. → Error Handling › PRODUCT_NOT_ENABLED - Permission. Your key must carry the endpoint's permission; otherwise
403 API_INSUFFICIENT_SCOPE.
| Permission | Endpoints |
|---|---|
bank-connect:view | Banks, connections, accounts (masked, without balances) and sync now |
bank-connect:transactions:view | Balances, transaction list and transaction detail |
bank-connect:transfer | Money 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:
- When you create or update a key: the
bank-connect:transferpermission can only be given to a key with an IP allowlist. Adding it while the list is empty is refused with400 organization.api_key.scope_requires_ip_allowlist. - 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 gets403 bank_connect.auth.ip_not_allowed. If the list cannot be verified for a moment, the request gets503 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
| Rule | Detail |
|---|---|
| Amounts | Integer in minor units (kuruş): "amountMinor": 150000 = ₺1,500.00. No decimals. |
| Currency | ISO 4217, e.g. TRY. |
| Times | ISO 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 field | An 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. |
| Identifiers | bankId, connectionId, accountId, transactionId are UUIDs. A malformed ID returns 404. |
| Masking | IBAN, account number and counterparty details are returned masked only. → Concepts › Masking |
| Unknown parameter | An undeclared query parameter or body field returns 400 VALIDATION_ERROR. |
| Money movement responses | In 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:
| Parameter | Description |
|---|---|
limit | Records per page, 1–100. Default 25. Above 100 returns 400. |
cursor | pageInfo.nextCursor from the previous response. Not sent on the first page. |
- If
pageInfo.hasMoreistrue, request the next page withcursor=<nextCursor>. - On these endpoints
pageInfo.totalCountis alwaysnull; even withincludeTotal=trueno total is returned.hasMoreis 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
- Banks you can connect: Banks
- Your accounts and freshness: Accounts
- Reading transactions and keeping in sync: Transactions
- Sending money: Money Transfers
- Buying and selling currency between your accounts: Currency Exchange