WhitepaperIssue token

Connect wallet

Connect wallet

Whitepaper
Issue token
Open Banking
TBank Open Banking API

Open Banking API — Integration Guide

The Open Banking API lets a partner service connect a client, act on their behalf after explicit consent, read accounts and balances, move money, issue and manage cards, and subscribe to events.

Base URL

EnvironmentBase URL
Sandboxhttps://sandbox-bank.tedr.com/open-banking/v1
Production<production base URL>/v1

There is no /rest segment in the path.A request looks like https://sandbox-bank.tedr.com/open-banking/v1/accounts.

The bank UI that hosts the OAuth consent page lives on a separate host — https://dev-tbank.tedr.com for the sandbox environment. You never call it directly: fetch it through GET /v1/oauth/deeplink instead of hard-coding it.

Production credentials and the production base URL are issued during onboarding.

Response envelope

Every successful response is wrapped in a status / data envelope. Paginated responses put the page inside data as nodes, count and cursor.

Pagination

All list endpoints accept pagination[limit] and pagination[skip].

Errors

Every error response carries a status of ERROR and an errors array — see Errors.

Response envelopeJSON
{ "status": "SUCCESS", "data": { } }
Paginated responseJSON
{
  "status": "SUCCESS",
  "data": { "nodes": [], "count": 0, "cursor": 0 }
}
Chapter 1

Connecting a client through OAuth 2.0

The partner needs access to the accounts and payments of a specific client, acting on that client's behalf. The client grants that access explicitly on the bank's consent page.

About OAuth 2.0.This is an authorization protocol — it lets the client grant the partner the right to act on their behalf without ever sharing their login and password. The client decides which set of permissions (scopes) to grant.

Verification comes first.Before granting access, the client must be verified — KYC for an individual, KYB for a company. Verification happens entirely in the bank's interface, before any interaction with the partner.

1 · Connecting a client through OAuth 2.0

Precondition — client verification before OAuth

By the time a client first reaches the partner's application and grants access, verification is already complete and the first virtual account already exists.

Path 1 — an individual connects a personal account

  1. The client registers in the bank UI.
  2. The client passes KYC — on success the system automatically creates their first virtual account.
  3. The client opens the partner's application and initiates the grant — the partner redirects them to the bank to confirm consent.
  4. After confirmation the partner receives a JWT bound to the client's personal account: sub = userId, no companyId.
  5. Every subsequent partner request runs on behalf of this client and touches only their personal accounts and payments.

Path 2 — a client registers a company and passes KYB

A company does not exist independently of a person — it is created by a client, who becomes its registrant.

  1. The client registers and passes KYC.
  2. The client creates a company and automatically becomes its registrant.
  3. The registrant completes KYB for the company.
  4. Once KYB is approved, a virtual account is created for the company automatically.
  5. The registrant initiates the grant in the partner's application and is redirected to the consent page.
  6. On the consent page the client chooses which account the grant applies to — their personal one or one of their companies. If a company is chosen, the JWT carries sub = userId of the registrant and a companyId.
  7. Every subsequent request runs for the company on behalf of the registrant.

Path 3 — connecting company employees

  1. Every new employee registers and passes KYC.
  2. The registrant adds employees, specifying a role and a share for each.
  3. Each employee can then grant access to the company independently, going through the OAuth flow separately.
  4. The resulting JWT carries sub = userId of the employee and the company's companyId.
1 · Connecting a client through OAuth 2.0

JWT structure

Every JWT is issued for a specific user, never for a company. The key difference is whether companyId is present.

ScenariosubcompanyId
Personal accountclient's userIdabsent
Company accountclient's userIdpresent

If companyId is present, all operations run in the company context on behalf of the user who authorized.

1 · Connecting a client through OAuth 2.0

Discover the available scopes (optional)

GETv1/oauth/scopes

Authorization: not required.

Query parameters: ids[], names[], scopes[], descriptions[], categories[], pagination[limit], pagination[skip].

category is INDIVIDUAL or CORPORATE.It marks which kind of account the scope applies to. It is not a grouping by module.

Scopes currently used by the API

ScopeGrants
tb:accounts:readRead accounts, balances and bank details.
tb:accounts:writeCreate accounts.
tb:payments:readRead payments.
tb:payments:writeCreate and send payments.
tb:tariffs:readRead tariffs, limits and fee calculations.
tb:personal-data:readRead the client's personal data.
tb:internal-cards:readRead cards.
tb:internal-cards:writeOrder and manage cards.
tb:external-cards:readRead Stripe cards and Stripe payment events.
RequestHTTP
GET https://sandbox-bank.tedr.com/open-banking/v1/oauth/scopes?pagination[skip]=0&pagination[limit]=20
ResponseJSON
{
  "status": "SUCCESS",
  "data": {
    "nodes": [
      {
        "id": "cc2d0454-0a68-4443-b71c-14e2b60ddd2e",
        "createdAt": "2026-01-15T10:00:00.000Z",
        "updatedAt": "2026-01-15T10:00:00.000Z",
        "name": "Read accounts",
        "description": "Grants read-only access to accounts.",
        "scope": "tb:accounts:read",
        "category": "INDIVIDUAL"
      }
    ],
    "count": 1,
    "cursor": 1
  }
}
1 · Connecting a client through OAuth 2.0

Build the authorization URL and redirect the client

ParameterRequiredDescription
responseTypeyesMust be code.
clientIdyesThe partner client id issued during onboarding.
redirectUriyesWhere the client is sent back after consent.
scopesyesComma-separated list of the scopes being requested.
stateyesAn arbitrary string the partner generates. It is returned unchanged on the redirect — verify it to protect against CSRF.
accountCategorynoLimits what the consent page offers: INDIVIDUAL — personal accounts only, CORPORATE — corporate accounts only, omitted — the client chooses.

An invalid clientId or redirectUri gives no redirect back.The sender cannot be identified, so the client is never returned to the partner.

URL templateText
{deeplink}?responseType=code
          &clientId={PARTNER_CLIENT_ID}
          &redirectUri={CALLBACK_URL}
          &scopes={COMMA_SEPARATED_SCOPES}
          &state={ARBITRARY_STRING}
          &accountCategory={INDIVIDUAL|CORPORATE}   // optional
ExampleText
https://dev-tbank.tedr.com/oauth/?responseType=code
  &clientId=<your-client-id>
  &redirectUri=https://partner-app.com/oauth/callback
  &scopes=tb:accounts:read,tb:accounts:write,tb:payments:read,tb:payments:write
  &state=random-csrf-token-xyz
  &accountCategory=CORPORATE
1 · Connecting a client through OAuth 2.0

Exchange the code for an access token

The code is valid for exactly 1 minute. Perform the exchange immediately after receiving the redirect.

POSTv1/oauth/access-token
FieldTypeDescription
grantTypereqstringAlways authorization_code.
codereqstringThe authorization code from the redirect.
redirectUrireqstringMust match the one used in the authorization URL.
clientIdreqstringThe partner client id.
clientSecretreqstringThe partner client secret. Keep it server-side.

Every failure returns the same code.An expired code, a reused code, a mismatched redirectUri or a wrong clientSecret all return OPEN-BANKING-029, without naming the specific cause. This prevents probing for the reason of a rejection.

RequestHTTP
POST https://sandbox-bank.tedr.com/open-banking/v1/oauth/access-token
Content-Type: application/json

{
  "grantType": "authorization_code",
  "code": "<authorization-code>",
  "redirectUri": "https://partner-app.com/oauth/callback",
  "clientId": "<your-client-id>",
  "clientSecret": "<your-client-secret>"
}
ResponseJSON
{
  "status": "SUCCESS",
  "data": {
    "accessToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
    "tokenType": "Bearer",
    "expiresIn": 31536000
  }
}
Decoded token payloadJSON
{
  "externalServiceId": "<your-client-id>",
  "scopes": ["tb:accounts:read", "tb:payments:write"],
  "sub": "c9788fca-ed84-49e9-a511-81af1906c393",
  "jti": "2b6df3c0-6cfb-4a57-ad61-86ab371384a3",
  "iss": "<issuer>",
  "iat": 1739981383,
  "exp": 1771517383
}
ErrorJSON
{
  "status": "ERROR",
  "errors": [{
    "title": "OAuth invalid payload for exchange code",
    "code": "OPEN-BANKING-029",
    "message": "Invalid payload was provided in exchange code for token."
  }]
}
1 · Connecting a client through OAuth 2.0

Using the access token

Every request to a client-scoped endpoint carries the bearer token.

The token identifies the client (sub), the partner (externalServiceId) and the granted scopes. Requests are additionally checked against the access-control service; a rejected request returns 403.

Webhook endpoints authenticate differently.The webhook management endpoints (Webhooks) use x-client-id and x-api-key instead of the bearer token, because subscriptions belong to the partner service, not to a client.

Authorization headerHTTP
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
Chapter 2

Working with accounts

Scope: tb:accounts:read / tb:accounts:write

The model

VirtualAccount

A named container. The account itself has no currency and no balance fields.

VirtualBalance

The balance in one specific currency. One account can hold several balances, one per currency.

Every balance carries three amounts: availableAmount (spendable now), holdAmount (locked against active operations) and totalAmount = availableAmount + holdAmount.

Money lives on balances, not on accounts.To see how much money an account holds, request its balances, not the account.

2 · Working with accounts

The first account already exists

When a client passes KYC (individual) or KYB (company), their first virtual account is created automatically. The partner does nothing.

2 · Working with accounts

List the client's accounts

GETv1/accounts

Query parameters

ParameterDescription
ids[]Filter by account UUIDs.
statuses[]ACTIVE, PROCESSING, BLOCKED, CLOSED.
categories[]INDIVIDUAL, CORPORATE.
types[]Account type, e.g. PERSONAL, BUSINESS.
tags[]Filter by tags.
excludeZeroBalancestrue — only accounts with a non-zero balance in at least one currency.
virtualBalanceCurrencyIds[]Only accounts holding a balance in the given currencies.
searchSearch across description and id.
pagination[limit] · pagination[skip]Pagination.

Technical hold accounts are never returned.

Timestamps are numbers.createdAt / updatedAt are Unix timestamps in milliseconds, not ISO strings.

RequestHTTP
GET https://sandbox-bank.tedr.com/open-banking/v1/accounts
Authorization: Bearer ...
ResponseJSON
{
  "status": "SUCCESS",
  "data": {
    "nodes": [
      {
        "id": "05155318-22a3-45aa-9db0-0ebfe8f21e8c",
        "description": "Main account",
        "status": "ACTIVE",
        "category": "INDIVIDUAL",
        "type": "PERSONAL",
        "ledgerType": "PASSIVE",
        "tag": "main_account",
        "createdAt": 1704067200000,
        "updatedAt": 1705312800000
      }
    ],
    "count": 1,
    "cursor": 1
  }
}
2 · Working with accounts

Create an additional account

POSTv1/accounts
FieldTypeDescription
descriptionoptstringAccount name / description.
currencyIdsoptUUID[]Currencies to open balances in; a zero balance is created for each. Obtain the ids from the Dictionary.

The field is description — not name.Both fields are optional; an account created without currencyIds has no balances until they are added.

The account is returned with status PROCESSING while it is being initialized at the provider, and becomes ACTIVE afterwards.

RequestHTTP
POST https://sandbox-bank.tedr.com/open-banking/v1/accounts
Authorization: Bearer ...
Content-Type: application/json

{
  "description": "Business account",
  "currencyIds": [
    "99d41c98-93d1-471a-a73a-4cac9e3587e7",
    "2e5d9800-f467-4eb7-ad47-1f67a874a908"
  ]
}
2 · Working with accounts

Read the account balances

GETv1/accounts/:accountId/balances

Query parameters: currencyIds[], currencyTickers[], statuses[], pagination[limit], pagination[skip].

The parent account is an object.It is returned as virtualAccount: { id } — not as a flat virtualAccountId string. There is no networkNames[] filter.

RequestHTTP
GET https://sandbox-bank.tedr.com/open-banking/v1/accounts/{accountId}/balances
Authorization: Bearer ...
ResponseJSON
{
  "status": "SUCCESS",
  "data": {
    "nodes": [
      {
        "id": "3f0f1f8a-0f4d-4c2c-9a6a-2d63bd8dd0f1",
        "virtualAccount": { "id": "05155318-22a3-45aa-9db0-0ebfe8f21e8c" },
        "currency": {
          "id": "99d41c98-93d1-471a-a73a-4cac9e3587e7",
          "ticker": "USD",
          "network": { "name": "FIAT" }
        },
        "availableAmount": "1150.50",
        "holdAmount": "100.00",
        "totalAmount": "1250.50",
        "description": null,
        "status": "ACTIVE",
        "createdAt": 1704067200000,
        "updatedAt": 1717237800000
      }
    ],
    "count": 1,
    "cursor": 1
  }
}
2 · Working with accounts

Read a single account

GETv1/accounts/:accountId

Returns the same object as the list endpoint. An account that does not belong to the client returns 404.

RequestHTTP
GET https://sandbox-bank.tedr.com/open-banking/v1/accounts/{accountId}
Authorization: Bearer ...
Chapter 3

Payments

Scope: tb:payments:write (create), tb:payments:read (read)

Supported payment systems: INNER, SWIFT, INTERNAL_CARDS. Each has its own endpoint and its own request shape, and each exposes four endpoints.

PurposeEndpoints
Create and send in one callPOST /v1/payments/inner · POST /v1/payments/swift · POST /v1/payments/internal-cards
Prepare without sendingPOST /v1/payments/inner/prepare · POST /v1/payments/swift/prepare · POST /v1/payments/internal-cards/prepare
Send a prepared paymentPOST /v1/payments/inner/{paymentId}/send · POST /v1/payments/swift/{paymentId}/send · POST /v1/payments/internal-cards/{paymentId}/send
Calculate the feePOST /v1/payments/inner/calculate-fee · POST /v1/payments/swift/calculate-fee · POST /v1/payments/internal-cards/calculate-fee

prepare and calculate-fee take the same body as the create call. send takes no body.

DRAFTPrepared but not sent yet.
PROCESSINGSubmitted and being processed.
ON_REVIEWHeld for manual review.
NEED_ACTIONWaiting for an action before it can continue.
SUCCESSFULCompleted.
REFUNDEDReturned to the sender.
DECLINEDRejected.

Payment direction is INCOMING or OUTGOING.

FieldTypeDescription
sourceCurrencyIdreqUUIDDebit currency.
targetCurrencyIdreqUUIDCredit currency.
sourceAmountstring (decimal)Amount to debit. Pass either this or targetAmount.
targetAmountstring (decimal)Amount to credit. Pass either this or sourceAmount.
quoteIdUUIDLocked quote id, required when sourceCurrencyId ≠ targetCurrencyId.
noteoptstringFree-form note.
metadataoptobjectFree-form JSON returned back on the payment object.

attachments is response-only.It is returned on the payment object but is not accepted in creation requests.

3 · Payments

About quoteId

For transfers between different currencies a quoteId is required — the id of a locked quote that fixes the exchange rate at the moment of sending. Quotes are issued by the Quotes service and have a limited lifetime. If the currencies match, no quoteId is needed.

3 · Payments

Internal transfer (INNER)

A transfer between accounts inside the system — instant, no external banks.

POSTv1/payments/inner
FieldTypeDescription
senderAccountIdreqUUIDThe account to debit.
recipientAccountIdreqUUIDThe account to credit.
recipientClientIdreqUUIDThe recipient client.
recipientClientTypereqINDIVIDUAL | CORPORATEThe kind of recipient client.
recipientEmailoptstringRecipient email.
recipientPhoneNumberoptstringRecipient phone number.
+ base fieldsSee base request fields.

There is no senderClientType field.The sender is taken from the token.

RequestHTTP
POST https://sandbox-bank.tedr.com/open-banking/v1/payments/inner
Authorization: Bearer ...
Content-Type: application/json

{
  "senderAccountId": "05155318-22a3-45aa-9db0-0ebfe8f21e8c",
  "recipientAccountId": "b2c3d4e5-f6a7-8901-bcde-f12345678901",
  "recipientClientId": "c9788fca-ed84-49e9-a511-81af1906c393",
  "recipientClientType": "INDIVIDUAL",
  "recipientEmail": "recipient@example.com",
  "recipientPhoneNumber": "+1234567890",
  "sourceCurrencyId": "99d41c98-93d1-471a-a73a-4cac9e3587e7",
  "targetCurrencyId": "99d41c98-93d1-471a-a73a-4cac9e3587e7",
  "sourceAmount": "100.00",
  "note": "Payment for services",
  "metadata": { "orderId": "ORD-123" }
}
3 · Payments

SWIFT transfer

POSTv1/payments/swift

Top level

FieldTypeDescription
purposeOfPaymentreqstringPurpose of the payment.
sourceOfFundsreqenumCOMMISSION_OF_SALES, DIVIDEND_INCOME_FROM_SHARE, INVESTMENT_CAPITAL, REVENUE_GENERATED_FROM_BUSINESS_ACTIVITIES, SALARY, SAVING.
internal.accountIdreqUUIDThe sender's account.
externalreqobjectThe recipient, see below.
+ base fieldsSee base request fields.

external

FieldTypeDescription
accountHolderNamereqstringName on the recipient account.
clientTypereqINDIVIDUAL | CORPORATEThe kind of recipient.
bankCodereqstringBIC, 8 or 11 characters.
accountNumberreqstringRecipient account number or IBAN.
branchCodeoptstringDerived from bankCode when omitted.
firstName · lastName · middleNameoptstringRecipient name parts.
addressreqobjectcountryId (UUID), postalCode, line1, line2?.
bankreqobjectname, countryId (UUID), cityName?, addressLine1?, addressLine2?, postalCode?.
intermediaryBankoptobjectbankCode, accountNumber?, name?, countryId?, cityName?, addressLine1?, addressLine2?.

Countries are passed as countryId (UUID).Not as { "alpha3": "DEU" } — that shape returns 400. Resolve the id through the Dictionary. Country objects with alpha2 / alpha3 appear only in responses.

RequestHTTP
POST https://sandbox-bank.tedr.com/open-banking/v1/payments/swift
Authorization: Bearer ...
Content-Type: application/json

{
  "purposeOfPayment": "Payment for consulting services",
  "sourceOfFunds": "REVENUE_GENERATED_FROM_BUSINESS_ACTIVITIES",
  "sourceCurrencyId": "99d41c98-93d1-471a-a73a-4cac9e3587e7",
  "targetCurrencyId": "2e5d9800-f467-4eb7-ad47-1f67a874a908",
  "sourceAmount": "1000.00",
  "quoteId": "7c9f2f2e-1a4b-4f1f-9a4e-58b0c2f1a3d2",
  "note": "Invoice #INV-2024-001",
  "metadata": { "orderId": "ORD-123" },
  "internal": {
    "accountId": "05155318-22a3-45aa-9db0-0ebfe8f21e8c"
  },
  "external": {
    "accountHolderName": "Acme Corp GmbH",
    "clientType": "CORPORATE",
    "bankCode": "DEUTDEFFXXX",
    "accountNumber": "DE89370400440532013000",
    "branchCode": "XXX",
    "firstName": null,
    "lastName": null,
    "middleName": null,
    "address": {
      "countryId": "6f0f2a51-1f4c-4a3e-9f4a-4b5b2d1c9a10",
      "postalCode": "10117",
      "line1": "Unter den Linden 1",
      "line2": null
    },
    "bank": {
      "name": "Deutsche Bank AG",
      "countryId": "6f0f2a51-1f4c-4a3e-9f4a-4b5b2d1c9a10",
      "cityName": "Berlin",
      "addressLine1": "Taunusanlage 12",
      "addressLine2": null,
      "postalCode": "60325"
    },
    "intermediaryBank": {
      "bankCode": "CHASUS33",
      "accountNumber": "9876543210",
      "name": "JPMorgan Chase",
      "countryId": "a1c3e5f7-2b4d-4e6f-8a0b-1c2d3e4f5a6b"
    }
  }
}
3 · Payments

Internal cards payment (INTERNAL_CARDS)

Moves money between the client's cards and accounts: top up a card from an account, withdraw from a card to an account, or transfer card to card.

POSTv1/payments/internal-cards
FieldTypeDescription
typereqACCOUNT_TO_CARD | CARD_TO_ACCOUNT | CARD_TO_CARDDirection of the operation.
senderreqobjectcardId?, accountId?, email?, phoneNumber? — fill the one matching type.
recipientreqobjectcardId?, accountId?, clientId (required), clientType (required), email?, phoneNumber?.
+ base fieldsSee base request fields.
RequestHTTP
POST https://sandbox-bank.tedr.com/open-banking/v1/payments/internal-cards
Authorization: Bearer ...
Content-Type: application/json

{
  "type": "CARD_TO_CARD",
  "sourceCurrencyId": "99d41c98-93d1-471a-a73a-4cac9e3587e7",
  "targetCurrencyId": "99d41c98-93d1-471a-a73a-4cac9e3587e7",
  "sourceAmount": "100.00",
  "note": "Card to card transfer",
  "metadata": { "orderId": "ORD-123" },
  "sender": {
    "cardId": "0f2d6d63-4d9e-4a01-9d0f-9a3b6f1f2c77"
  },
  "recipient": {
    "cardId": "c1b2a3d4-5e6f-4a7b-8c9d-0e1f2a3b4c5d",
    "clientId": "c9788fca-ed84-49e9-a511-81af1906c393",
    "clientType": "INDIVIDUAL"
  }
}
3 · Payments

Calculate the fee

The body is the full body of the future payment.

POSTv1/payments/swift/calculate-fee

Each payment system has its own calculate-fee endpoint: /v1/payments/inner/calculate-fee, /v1/payments/swift/calculate-fee and /v1/payments/internal-cards/calculate-fee.

RequestHTTP
POST https://sandbox-bank.tedr.com/open-banking/v1/payments/swift/calculate-fee
Authorization: Bearer ...
Content-Type: application/json
ResponseJSON
{
  "status": "SUCCESS",
  "data": {
    "totalFee": "10.00",
    "totalAmount": "1010.00",
    "currencyTicker": "USD",
    "originalAmount": "1000.00",
    "originalTotalAmount": "1010.00",
    "originalTotalFee": "10.00",
    "originalProviderFee": "7.00",
    "originalBankFee": "3.00"
  }
}
3 · Payments

Browse payments

GETv1/payments

Query parameters

ParameterDescription
paymentSystems[]INNER, SWIFT, INTERNAL_CARDS.
statuses[]DRAFT, PROCESSING, ON_REVIEW, NEED_ACTION, SUCCESSFUL, REFUNDED, DECLINED.
directions[]INCOMING, OUTGOING.
dateRange[from] · dateRange[to]Range over the creation date.
settledAt[from] · settledAt[to]Range over the settlement date.
currencyIds[]By currency on either side.
sourceCurrencyIds[]By debit currency.
targetCurrencyIds[]By credit currency.
isFavoriteFavourites only.
searchFull-text search.
pagination[limit] · pagination[skip]Pagination.

Filters that do not exist.There are no massPaymentIds[], commentIds[] or scoringRiskLevels[] filters.

GET /v1/payments/{paymentId} returns a single payment; its shape depends on paymentSystem.

RequestHTTP
GET https://sandbox-bank.tedr.com/open-banking/v1/payments
  ?paymentSystems[]=SWIFT
  &directions[]=OUTGOING
  &dateRange[from]=2026-01-01T00:00:00Z
Authorization: Bearer ...
3 · Payments

Payment object

Shared by all payment systems.

FieldTypeDescription
idUUIDPayment id.
paymentSystemenumINNER, SWIFT, INTERNAL_CARDS.
statusenumCurrent status.
previousStatusenumPrevious status, when the payment has changed state.
directionenumINCOMING, OUTGOING.
isFavoritebooleanFavourite flag.
notestringFree-form note.
purposeOfPaymentstringPurpose of the payment.
attachmentsarrayAttached files. Response-only.
metadataobject | nullWhatever was sent on creation.
sender · recipientobjectclientId, clientType.
amount · fee · totalAmountobjectEach with exchangeRate, source, target.
createdAt · updatedAt · settleddateLifecycle timestamps.

Per-system additions: INNER and INTERNAL_CARDS add senderClient / recipientClient; INTERNAL_CARDS also adds type; SWIFT adds sourceOfFunds, internal and external with the full bank details.

Chapter 4

Bank details for incoming transfers

Scope: tb:accounts:read — the client wants to receive an incoming transfer; the partner requests the details to pass to the sender.

GETv1/accounts/:accountId/bank-details

currencyIds is optional — without it the details for every currency of the account are returned.

How to read routes

RouteUse
INNERFor receiving from another user of the system; passing the sender the virtualAccountId is enough.
SWIFTFor an international bank transfer; pass on beneficiary and beneficiaryBank.
BSB · IFSCThe same idea for Australia / India, when the account has such a route.

One currency can expose several routes at once; the sender picks whichever suits them.

The values shown here are placeholders.Real beneficiary details are returned per account and must not be reproduced as examples in the documentation.

RequestHTTP
GET https://sandbox-bank.tedr.com/open-banking/v1/accounts/{accountId}/bank-details
  ?currencyIds[]=99d41c98-93d1-471a-a73a-4cac9e3587e7
Authorization: Bearer ...
ResponseJSON
{
  "status": "SUCCESS",
  "data": {
    "virtualAccountId": "05155318-22a3-45aa-9db0-0ebfe8f21e8c",
    "bankDetails": [
      {
        "currency": {
          "id": "99d41c98-93d1-471a-a73a-4cac9e3587e7",
          "ticker": "USD",
          "network": { "name": "FIAT" }
        },
        "routes": [
          {
            "schema": "INNER",
            "details": {
              "virtualAccountId": "05155318-22a3-45aa-9db0-0ebfe8f21e8c"
            }
          },
          {
            "schema": "SWIFT",
            "details": {
              "beneficiary": {
                "name": "Example Holdings Ltd",
                "address": "1 Example Street, Exampletown, EX1 2AB"
              },
              "beneficiaryBank": {
                "account": "0000000000",
                "code": "EXMPUS33XXX",
                "name": "EXAMPLE BANK",
                "address": null,
                "country": {
                  "alpha2": "US",
                  "alpha3": "USA",
                  "name": "United States"
                }
              },
              "correspondentBanks": [],
              "remittanceInformation": [
                "For own account transfer",
                "Actual purpose of payment"
              ]
            }
          }
        ]
      }
    ]
  }
}
Chapter 5

Cards

Base path: /v1/cards · Scope: tb:internal-cards:read / tb:internal-cards:write

Order physical and virtual cards, read their details in encrypted form, and manage their state.

Card data never travels in the clear.Card details and the PIN are encrypted with the public key from GET /v1/cards/public-key — both when you read them and when you set a new PIN.

5 · Cards

v1/cards/public-key

Returns the public key used to encrypt card data (PIN) before sending it, and to receive encrypted card details.

GETv1/cards/public-key

Authorization: not required.

ResponseJSON
{
  "status": "SUCCESS",
  "data": { "publicKey": "-----BEGIN PUBLIC KEY-----..." }
}
5 · Cards

v1/cards/can-order-card

Whether the current client may order a card (tariff, KYC status and so on).

GETv1/cards/can-order-card
ResponseJSON
{ "status": "SUCCESS", "data": { "result": true } }
5 · Cards

v1/cards/programs

Available card programs.

GETv1/cards/programs
GETv1/cards/programs/:programId

Query: ids[], pagination[limit], pagination[skip].

FieldDescription
id · name · descriptionProgram identity.
currencies[]Currencies the program can be issued in.
processingTypeVISA or MASTERCARD.
cardTypePHYSICAL or VIRTUAL.
costissue, service, currency.
RequestHTTP
GET https://sandbox-bank.tedr.com/open-banking/v1/cards/programs
Authorization: Bearer ...
5 · Cards

v1/cards/delivery-methods

Available delivery methods.

GETv1/cards/delivery-methods
GETv1/cards/delivery-methods/:deliveryMethodId

Query: ids[], pagination[limit], pagination[skip]. Each method contains id, name, description and cost (amount, currency).

RequestHTTP
GET https://sandbox-bank.tedr.com/open-banking/v1/cards/delivery-methods
Authorization: Bearer ...
POSTv1/cards
FieldTypeDescription
programIdreqUUIDCard program.
cardholderNamereqstringName embossed on the card.
phoneNumberreqstringCardholder phone.
deliveryoptobjectRequired for physical cards.
delivery.deliveryMethodIdreqUUIDRequired inside delivery.
delivery.address.line1reqstringStreet address.
delivery.address.line2optstringApartment, suite and so on.
delivery.address.cityreqstringCity.
delivery.address.countryCodereqstringISO alpha-3, e.g. DEU.
delivery.address.zipCodereqstringPostal code.
delivery.address.regionoptstringRegion or state.
delivery.address.nameOrCompanyNamereqstringRecipient of the delivery.

The delivery data is nested.The body is not { programId, deliveryMethodId, deliveryAddress, phoneNumber }, and cardholderName is required.

The card is returned with orderStatus: PROCESSING.

RequestHTTP
POST https://sandbox-bank.tedr.com/open-banking/v1/cards
Authorization: Bearer ...
Content-Type: application/json

{
  "programId": "80d9a3a3-787c-4475-bf2c-14e05424acac",
  "cardholderName": "DOMINIC HOPKINS",
  "phoneNumber": "+1234567890",
  "delivery": {
    "deliveryMethodId": "5f4d42e5-60cb-42bc-b0d4-2b73d063560f",
    "address": {
      "line1": "50 St Denys Road",
      "line2": "Apt. 5",
      "city": "Adlington",
      "countryCode": "DEU",
      "zipCode": "32584",
      "region": "England",
      "nameOrCompanyName": "Dominic Hopkins"
    }
  }
}
GETv1/cards
GETv1/cards/:cardId

Query: ids[], orderStatuses[], states[], types[], pagination[limit], pagination[skip].

There is no statuses[] filter.A card has two independent status fields — orderStatus and state — and they are filtered separately.

Status fields

FieldValues
orderStatusPROCESSING, WAITING_PAY, CREATING_CARD, DISPATCHED, ACTIVATION_CARD, APPROVED, DECLINED.
stateACTIVATION_IN_PROGRESS, ACTIVE, INACTIVE, BLOCKED, CLOSED, EXPIRED.
typePHYSICAL or VIRTUAL.

Card object

FieldTypeDescription
idUUIDCard id.
clientIdUUIDOwner.
programIdUUIDCard program.
holderNamestringName embossed on the card.
holderPhonestringCardholder phone.
orderStatusenumWhere the order stands.
stateenumWhether the card can be used.
typePHYSICAL | VIRTUALCard form factor.
processingTypeVISA | MASTERCARDPayment network.
isDefaultbooleanThe client's default card.
virtualAccountIdUUID | nullLinked account, when there is one.
RequestHTTP
GET https://sandbox-bank.tedr.com/open-banking/v1/cards?states[]=ACTIVE&types[]=VIRTUAL
Authorization: Bearer ...
5 · Cards

Card requisites and PIN

Return the card details (number, CVV, expiry) and the PIN, encrypted with the public key passed in the path.

GETv1/cards/:cardId/requisites/:publicKey
GETv1/cards/:cardId/pin-code/:publicKey

Decrypt the returned string with your private key on your side.

ResponseJSON
{
  "status": "SUCCESS",
  "data": { "requisites": "<encrypted string>" }
}
5 · Cards

v1/cards/:cardId/phone-number

Update the cardholder's phone number.

PATCHv1/cards/:cardId/phone-number
Request bodyJSON
{ "phoneNumber": "+1234567890" }
5 · Cards

v1/cards/:cardId/pin-code

Set a new PIN.

PATCHv1/cards/:cardId/pin-code

The PIN is transmitted encrypted, never as plain digits.Encrypt it with the key from GET /v1/cards/public-key. Sending "1234" will be rejected by the card processor.

Request bodyJSON
{ "pin": "<PIN encrypted with the public key>" }
5 · Cards

Activate, block, unblock and close

No body. All four return the updated card object.

PATCHv1/cards/:cardId/active
PATCHv1/cards/:cardId/block
PATCHv1/cards/:cardId/unblock
PATCHv1/cards/:cardId/close

Closing a card is irreversible.A closed card cannot be reactivated — order a new one instead.

RequestHTTP
PATCH https://sandbox-bank.tedr.com/open-banking/v1/cards/{cardId}/block
Authorization: Bearer ...
Chapter 6

v1/tariffs/limits/check

Scope: tb:tariffs:read — check whether an operation fits within the client's limits.

GETv1/tariffs/limits/check

Query parameters

FieldTypeDescription
operationNamereqenumOUTGOING or INCOMING.
amountreqstringAmount to check.
currencyTickerreqstringFor example USD, EUR.

The parameter is operationName, not operationType.Its values are OUTGOING / INCOMING — not SWIFT_OUTGOING / INNER. The response is not { allowed, remainingAmount, currency }.

RequestHTTP
GET https://sandbox-bank.tedr.com/open-banking/v1/tariffs/limits/check
  ?operationName=OUTGOING
  &amount=500.00
  &currencyTicker=EUR
Authorization: Bearer ...
ResponseJSON
{
  "status": "SUCCESS",
  "data": {
    "result": true,
    "reason": null,
    "expect": {
      "available": "4500.00",
      "min": "1.00",
      "max": "10000.00"
    },
    "source": { "currencyTicker": "EUR", "amount": "500.00" },
    "target": { "currencyTicker": "EUR", "amount": "500.00" },
    "availableLimits": {
      "daily": "4500.00",
      "weekly": "20000.00",
      "monthly": "50000.00",
      "annual": "500000.00"
    }
  }
}
6 · Tariffs and limits

v1/tariffs/limits

GETv1/tariffs/limits
GETv1/tariffs/limits/:limitId

Query: ids[], clientIds[], pagination[limit], pagination[skip]. There is no operationTypes[] filter.

Limit object

id, clientId, name, description, isBaseCurrency, daily, weekly, monthly, annual, min, max, and available (daily, weekly, monthly, annual).

RequestHTTP
GET https://sandbox-bank.tedr.com/open-banking/v1/tariffs/limits
Authorization: Bearer ...
GETv1/tariffs/:clientId/current-tariff

The response contains the tariff itself only.Limits and fees are requested separately — see limits and fee calculation.

ResponseJSON
{
  "status": "SUCCESS",
  "data": {
    "id": "9e6f1a2b-3c4d-4e5f-8a9b-0c1d2e3f4a5b",
    "name": "Standard",
    "description": null,
    "billingPeriod": "MONTHLY"
  }
}
6 · Tariffs and limits

v1/tariffs/fees/calculate

GETv1/tariffs/fees/calculate

Query parameters

FieldTypeDescription
operationNamereqenumINCOMING, OUTGOING, STRIPE_SHOPPING or STRIPE_TOP_UP.
amountreqstringAmount to calculate the fee for.
currencyTickerreqstringFor example USD, EUR.
inclusiveInAmountoptbooleanWhether the fee is included in amount.
ResponseJSON
{
  "status": "SUCCESS",
  "data": {
    "source": {
      "currencyTicker": "EUR",
      "amount": "100.00",
      "totalAmount": "105.00",
      "totalFee": "5.00"
    },
    "target": {
      "currencyTicker": "EUR",
      "amount": "100.00",
      "totalAmount": "105.00",
      "totalFee": "5.00"
    }
  }
}
Chapter 7

v1/stripe/public-key

Base path: /v1/stripe · Scope: tb:external-cards:read — Stripe is used to top an account up from an external bank card.

GETv1/stripe/public-key

Returns the Stripe publishable key for initialising Stripe.js or the Stripe SDK on your side. Authorization: not required.

RequestHTTP
GET https://sandbox-bank.tedr.com/open-banking/v1/stripe/public-key
7 · Stripe (external cards)

v1/stripe/cards

Attach an external card to the client.

POSTv1/stripe/cards

Card data never passes through this API.cardTokenId is the token returned by the Stripe SDK after the client enters their card data.

Request bodyJSON
{ "cardTokenId": "tok_1QpQqOCUHBOJJ0oY9zGxGYtQ" }
7 · Stripe (external cards)

v1/stripe/cards — list and read

GETv1/stripe/cards
GETv1/stripe/cards/:cardId

Query: ids[], pagination[limit], pagination[skip].

Card object: id, clientId, brand, country, expMonth, expYear, name.

RequestHTTP
GET https://sandbox-bank.tedr.com/open-banking/v1/stripe/cards
Authorization: Bearer ...
7 · Stripe (external cards)

v1/stripe/payment-intent

Top a virtual account up from an attached Stripe card.

POSTv1/stripe/payment-intent
FieldTypeDescription
amountreqstringTop-up amount.
creditorAccountIdreqUUIDVirtual account to credit.
debitorCardIdreqstringAttached Stripe card.
descriptionoptstringShown on the Stripe payment.

Returns { "id": "pi_…", "clientId": "…" }. The fee is taken from the client's tariff, taking the card's country into account.

Request bodyJSON
{
  "amount": "100",
  "creditorAccountId": "f1df072f-d728-4038-8d1f-f569eaafa92d",
  "debitorCardId": "card_1QpQqOCUHBOJJ0oYUYH7TKeJ",
  "description": "Top-up via Stripe"
}
Chapter 8

v1/dictionary/currencies

Reference data needed to build requests: every currencyId and countryId used in payments and account creation comes from here.

GETv1/dictionary/currencies
GETv1/dictionary/currencies/:currencyId

Query: pagination[limit], pagination[skip].

Resolve ids once and cache them.currencyIds are required by account creation and by every payment request.

ResponseJSON
{
  "status": "SUCCESS",
  "data": {
    "nodes": [
      {
        "id": "99d41c98-93d1-471a-a73a-4cac9e3587e7",
        "name": "US Dollar",
        "ticker": "USD",
        "precision": 2,
        "type": "FIAT",
        "symbol": "$"
      }
    ],
    "count": 1,
    "cursor": 1
  }
}
GETv1/dictionary/countries
GETv1/dictionary/countries/:countryId

Query: pagination[limit], pagination[skip].

SWIFT payments take countryId, not an alpha code.Resolve the id here before building an SWIFT request.

ResponseJSON
{
  "status": "SUCCESS",
  "data": {
    "nodes": [
      {
        "id": "6f0f2a51-1f4c-4a3e-9f4a-4b5b2d1c9a10",
        "name": "Germany",
        "code": "DEU",
        "phoneCode": "+49"
      }
    ],
    "count": 1,
    "cursor": 1
  }
}
Chapter 9

Webhooks

Subscribe to events (accounts, payments, cards) and receive POST notifications on your own URL. Managing subscriptions is a CRUD over a Webhook object.

Authentication

These endpoints are authenticated with partner-level headers, not with a client bearer token.

FieldTypeDescription
x-client-idreqUUIDYour client id.
x-api-keyreqUUIDYour API key.

Both headers are required on every request in this chapter.A missing or malformed header returns 403.

Endpoints

MethodPathPurpose
POST/v1/webhooksCreate a subscription.
GET/v1/webhooksList subscriptions (paginated).
GET/v1/webhooks/{webhookId}Read one subscription.
PATCH/v1/webhooks/{webhookId}Update a subscription.
DELETE/v1/webhooks/{webhookId}Delete a subscription (irreversible).
Partner headersHTTP
x-client-id: <your-client-id>
x-api-key: <your-api-key>
POSTv1/webhooks
FieldTypeDescription
urlreqstring (URL)Where the POST notifications are delivered.
secretreqstringSecret used to sign outgoing notifications; never returned back.
eventsreqenum[]At least one event from the catalogue.
isActivereqbooleanWhether the subscription is enabled.
descriptionoptstringFree-form description.

secret is not returned.The response is the subscription object — id, createdAt, description, isActive, url, events. Store the secret on your side.

RequestHTTP
POST https://sandbox-bank.tedr.com/open-banking/v1/webhooks
x-client-id: <your-client-id>
x-api-key: <your-api-key>
Content-Type: application/json

{
  "url": "https://partner-app.com/webhooks/payments",
  "secret": "<your-signing-secret>",
  "events": ["INNER_PAYMENT_SUCCEEDED", "ACCOUNTS_CREATED"],
  "isActive": true,
  "description": "Webhook to notify when payments are updated."
}
GETv1/webhooks
GETv1/webhooks/:webhookId

Query: ids[], pagination[limit], pagination[skip]. Only subscriptions belonging to your x-client-id are returned. events come back sorted alphabetically.

RequestHTTP
GET https://sandbox-bank.tedr.com/open-banking/v1/webhooks?pagination[skip]=0&pagination[limit]=10
x-client-id: <your-client-id>
x-api-key: <your-api-key>
9 · Webhooks

Update and delete

PATCHv1/webhooks/:webhookId
DELETEv1/webhooks/:webhookId

All fields are optional; only what is sent is changed.

The event list is modified incrementally.Use attach / detach — the list is not overwritten with a new array, and at least one event must remain attached.

DELETE /v1/webhooks/{webhookId} removes the subscription permanently and returns the deleted object.

Request bodyJSON
{
  "isActive": false,
  "url": "https://partner-app.com/webhooks/payments-v2",
  "description": "Updated webhook.",
  "events": {
    "attach": ["SWIFT_PAYMENT_SUCCEEDED"],
    "detach": ["ACCOUNTS_CREATED"]
  }
}
9 · Webhooks

Event catalogue

A notification is delivered only to partners that have both an active subscription and the matching scope granted by the client.

GroupEventsScope required
AccountsACCOUNTS_ORDERED, ACCOUNTS_CREATEDtb:accounts:read
INNER paymentsINNER_PAYMENT_CREATED / _UPDATED / _SUCCEEDED / _DECLINED / _REFUNDEDtb:payments:read
SWIFT paymentsSWIFT_PAYMENT_CREATED / _UPDATED / _SUCCEEDED / _DECLINED / _REFUNDEDtb:payments:read
INTERNAL_CARDS paymentsINTERNAL_CARDS_PAYMENT_CREATED / _UPDATED / _SUCCEEDED / _DECLINED / _REFUNDEDtb:payments:read
CardsINTERNAL_CARD_ORDERED, INTERNAL_CARD_UPDATED, INTERNAL_CARD_EXPIRES, INTERNAL_CARD_EXPIREDtb:internal-cards:read
Stripe cardsSTRIPE_CARD_CREATED, STRIPE_CARD_EXPIRES, STRIPE_CARD_EXPIREDtb:external-cards:read
Stripe paymentsSTRIPE_PAYMENT_CREATED, STRIPE_PAYMENT_UPDATEDtb:external-cards:read
FieldTypeDescription
idUUIDUnique id of this delivery — use it for idempotency.
webhookIdUUIDThe subscription the notification belongs to.
eventstringEvent name from the catalogue.
timestampnumberUnix time in seconds.
dataobjectThe entity snapshot: the payment, the account list or the card.

Headers

HeaderValue
Content-Typeapplication/json
X-SIGNATUREHMAC-SHA1(secret, raw body) in hex, where secret is the one you set on the subscription.

Compute the HMAC over the raw body.Sign the bytes exactly as received, before any JSON parsing.

What your endpoint must return

Any 2xx status counts as a successful delivery. The response body is ignored — 200 with an empty body, 204, or 200 with any JSON all work equally.

Delivery guarantees

Deliveries are at-least-once: the same event can arrive more than once, so deduplicate by id. The order of deliveries is not guaranteed.

Retries

The response timeout is 10 seconds; a timeout counts as a failed delivery. Failed deliveries are retried on a fixed schedule — 20 attempts in total: three after 15 s, one after 30 s, three after 60 s, two after 320 s, five after ~2.8 h, five after 12 h and a final one after 24 h.

An exhausted schedule disables the subscription.It is switched to isActive: false with the reason RETRY_TIMEOUT — fix the receiver and re-enable it with PATCH.

Notification bodyJSON
{
  "id": "9b1c8f0e-7d3a-4e2b-8a11-6c5d4e3f2a10",
  "webhookId": "5f4d42e5-60cb-42bc-b0d4-2b73d063560f",
  "event": "INNER_PAYMENT_SUCCEEDED",
  "timestamp": 1748689100,
  "data": { }
}
Verifying the signatureNode
const crypto = require('crypto')
const expected = crypto
  .createHmac('sha1', SECRET)
  .update(rawBody)
  .digest('hex')
const ok = expected === req.headers['x-signature']
Retry schedule (seconds)Text
15, 15, 15, 30, 60, 60, 60, 320, 320, 10240 × 5, 43200 × 5, 86400
10 · Errors

Error format

Each error object carries title, code and message.

HTTP statusWhen
400Field validation failed.
403The access-control service denied the request, or x-client-id / x-api-key are missing on webhook endpoints.
404The account, payment or card does not exist or does not belong to the client.
500Internal error, or the provider is unavailable.
Error responseJSON
{
  "status": "ERROR",
  "errors": [
    {
      "title": "Account not found",
      "code": "OPEN-BANKING-011",
      "message": "Account not found."
    }
  ]
}
CodeMeaning
OPEN-BANKING-003Forbidden.
OPEN-BANKING-004Validation failed.
OPEN-BANKING-008Currency not found.
OPEN-BANKING-009Country not found.
OPEN-BANKING-011Account not found.
OPEN-BANKING-012Card not found.
OPEN-BANKING-029Invalid payload in the code-for-token exchange.
OPEN-BANKING-030Invalid authorization token.
OPEN-BANKING-033Payment not found.
OPEN-BANKING-034Payment is in an invalid state for this operation.
OPEN-BANKING-038User not found.
OPEN-BANKING-042Webhook not found.
OPEN-BANKING-046Unsupported currency.
OPEN-BANKING-047Limit not found.
OPEN-BANKING-048Limit check failed.
OPEN-BANKING-050Fee calculation failed.
OPEN-BANKING-051Card is in an invalid state for this operation.
OPEN-BANKING-053Route not found.
API v1