Sandbox emulates a minimal bank, to provide EBICS access to test Taler. The top-level API defines two main groups: demobanks and legacy. Currently, error types are common to both groups.
Sandbox is designed to allow multiple demobanks being hosted,
where every demobank can have its own configuration (including
a different currency). A demobank has a name, although currently
only one demobank, named default
, is supported. Such demobank
activates the API segment /demobanks/default
, under which several
APIs are then served. The following sections describe all such APIs.
This API offers to manage a selected group of users who act as businesses for a local currency. Policies to disincentivize cashout operations may also apply, making therefore this setup a circuit within a wider traditional currency.
For brevity, the list of response statuses for each endpoint may not be exhaustive.
Note
This API requires to disable ordinary registrations in the
configuration, to avoid other APIs from circumventing this registration
policy. See libeufin-sandbox config --help
.
The following endpoints are served under /demobanks/default/circuit-api
.
The client side of this API is offered both along the
CLI and the SPA. The SPA is generally served
along Sandbox /
path.
POST
/accounts
¶Create a new bank account. Only the administrator is allowed.
Request:
interface CircuitAccountRequest {
// Username
username: string;
// Password.
password: string;
// Addresses where to send the TAN. If
// this field is missing, then the cashout
// won't succeed.
contact_data: CircuitContactData;
// Legal subject owning the account.
name: string;
// 'payto' address pointing the bank account
// where to send payments, in case the user
// wants to convert the local currency back
// to fiat.
cashout_address: string;
// IBAN of this bank account, which is therefore
// internal to the circuit. Randomly generated,
// when it is not given.
internal_iban?: string;
}
interface CircuitContactData {
// E-Mail address
email?: EmailAddress;
// Phone number.
phone?: PhoneNumber;
}
Response:
admin
or bank
.DELETE
/accounts/$username
¶Delete the account whose username is $username
. The deletion
succeeds only if the balance is zero. Only the administrator is
allowed.
Response:
admin
or bank
.PATCH
/accounts/$username
¶Allows the administrator and the user to reconfigure
the account data of $username
.
Note
Only the administrator has the rights to change the user legal name.
Request:
interface CircuitAccountReconfiguration {
// Addresses where to send the TAN.
contact_data: CircuitContactData;
// Optional field. 'Payto' address pointing the
// bank account where to send payments, in case the
// user wants to convert the local currency back
// to fiat. If this field is missing, any cash-out
// address will be deleted and the user loses their
// cash-out capability.
cashout_address?: string;
// Legal name associated with $username. NOTE:
// only the administrator can change this value.
name?: string;
}
Response:
$username
are not sufficient.
That includes the case where a correctly authenticated
user tries to change their legal name. It also
includes the case where ‘admin’ tries to change its
own account.$username
was not found.PATCH
/accounts/$username/auth
¶Allows administrators and ordinary users to change the account’s password.
Request:
interface AccountPasswordChange {
// New password.
new_password: string;
}
Response:
GET
/accounts
¶Obtains a list of the accounts registered at the bank. It returns only the information that this API handles, without any balance or transactions list. The Access API offers that. This request is only available to the administrator.
Request:
Response:
interfaces CircuitAccounts {
customers: CircuitAccountMinimalData[];
}
interface Balance {
amount: Amount;
credit_debit_indicator: "credit" | "debit";
}
interface CircuitAccountMinimalData {
// Username
username: string;
// Legal subject owning the account.
name: string;
// current balance of the account
balance: Balance;
// Number indicating the max debit allowed for the requesting user.
debitThreshold: string;
}
GET
/accounts/$username
¶Obtains information relative to the account owned by
$username
. The request is available to the administrator
and $username
itself.
Response:
interface CircuitAccountData {
// Username
username: string;
// IBAN hosted at Libeufin Sandbox
iban: string;
contact_data: CircuitContactData;
// Legal subject owning the account.
name: string;
// 'payto' address pointing the bank account
// where to send cashouts. This field is optional
// because not all the accounts are required to participate
// in the merchants' circuit. One example is the exchange:
// that never cashouts. Registering these accounts can
// be done via the access API.
cashout_address?: string;
}
POST
/cashouts
¶Initiates a conversion to fiat currency. The account to be credited is the one specified at registration time via the cashout_address parameter. The account to be debited is extracted from the authentication credentials. The bank sends a TAN to the customer to let them confirm the operation. The request is only available to ordinary users, not to the administrator.
Note
Consult the cashout rates call to learn about any applicable fee or exchange rate.
To test this operation without relying on any SMS/E-mail provider,
Libeufin offers two methods: defining an environment variable called
LIBEUFIN_CASHOUT_TEST_TAN
or specifying the value file
to
the tan_channel
field of the request object.
Assuming LIBEUFIN_CASHOUT_TEST_TAN
is set to T, every /confirm
operation can use T as the TAN. Setting instead the tan_channel
field to file
will cause the server to (over)write every TAN to
/tmp/libeufin-cashout-tan.txt
. If both are used, the environment
variable takes the precedence.
Request:
enum TanChannel {
SMS = "sms",
EMAIL = "email",
FILE = "file"
}
interface CashoutRequest {
// Optional subject to associate to the
// cashout operation. This data will appear
// as the incoming wire transfer subject in
// the user's external bank account.
subject?: string;
// That is the plain amount that the user specified
// to cashout. Its $currency is the circuit currency.
amount_debit: Amount;
// That is the amount that will effectively be
// transferred by the bank to the user's bank
// account, that is external to the circuit.
// It is expressed in the fiat currency and
// is calculated after the cashout fee and the
// exchange rate. See the /cashout-rates call.
amount_credit: Amount;
// Which channel the TAN should be sent to. If
// this field is missing, it defaults to SMS.
// The default choice prefers to change the communication
// channel respect to the one used to issue this request.
tan_channel?: TanChannel;
}
Response:
interface CashoutPending {
// UUID identifying the operation being created
// and now waiting for the TAN confirmation.
uuid: string;
}
admin
or bank
) tried the operation.POST
/cashouts/$cashoutId/abort
¶Aborts the $cashoutId
operation. Original author
and admin are both allowed.
Response:
$cashoutId
was found in the pending state
and got successfully aborted.$cashoutId
is not found. Note: that happens
also when $cashoutId
got aborted before this request.$cashoutId
was already confirmed.POST
/cashouts/$cashoutId/confirm
¶Confirms the $cashoutId
operation by accepting its
TAN. The request should still be authenticated with
the users credentials. Only the original author is allowed.
Request:
interface CashoutConfirm {
// the TAN that confirms $cashoutId.
tan: string;
}
Response:
$cashoutId
was found in the pending state and
got successfully confirmed.$cashoutId
is not found. Note: that happens
also when $cashoutId
got aborted before this request.admin
or bank
) tried the operation
* the user changed their cash-out address between the creation and the confirmation of $cashoutId
.$cashoutId
was already confirmed.GET
/cashouts/estimates
¶This endpoint shows how the bank would apply the cash-out ratio and fee to one input amount. Typically, frontends ask this endpoint before creating cash-out operations. There is no financial consequence to this endpoint and only registered users are allowed to request. At least one of the two query parameters should be provided. If both are given, then the server checks their correctness. Amounts must include the currency.
Request:
Response:
GET
/config
¶Response:
interface Config {
// Name of this API, always "circuit".
name: string;
// API version in the form $n:$n:$n
version: string;
// Contains ratios and fees related to buying
// and selling the circuit currency.
ratios_and_fees: RatiosAndFees;
// Fiat currency. That is the currency in which
// cash-out operations ultimately wire money.
fiat_currency: string;
}
interface RatiosAndFees {
// Exchange rate to buy the circuit currency from fiat.
buy_at_ratio: LibeufinNumber;
// Exchange rate to sell the circuit currency for fiat.
sell_at_ratio: LibeufinNumber;
// Fee to subtract after applying the buy ratio.
buy_in_fee: LibeufinNumber;
// Fee to subtract after applying the sell ratio.
sell_out_fee: LibeufinNumber;
}
Example. Given a circuit currency CC, a fiat currency FC, a sell_at_ratio = 0.9 and sell_out_fee = 0.03, selling 10 CC would result in the following FC: (10 * 0.9) - 0.03 = 8.97 FC. On the other hand, given buy_at_ratio = 1.1 and buy_in_fee = 0.01, a user wanting to spend 10 FC to buy the CC would result in the following CC: (10 * 1.1) - 0.01 = 10.99 CC.
Note
the terms ‘sell out’ and ‘cashout’ may be used interchangeably.
GET
/cashouts
¶Returns the list of all the (pending and confirmed) cash-out operations. Ordinary users can only use this endpoint to learn their own cash-out operations.
Request:
Response:
interface Cashouts {
// Every string represents a cash-out operation UUID.
cashouts: string[];
}
account
parameter or by passing to it someone
else’s username.GET
/cashouts/$cashoutId
¶Informs about the status of the $cashoutId
operation.
The request is available to the administrator and the original author.
Response:
interface CashoutStatusResponse {
status: CashoutStatus;
// Amount debited to the circuit bank account.
amount_debit: Amount;
// Amount credited to the external bank account.
amount_credit: Amount;
// Transaction subject.
subject: string;
// Circuit bank account that created the cash-out.
account: string;
// Fiat bank account that will receive the cashed out amount.
cashout_address: string;
// Ratios and fees related to this cash-out at the time
// when the operation was created.
ratios_and_fees: RatiosAndFees;
// Time when the cash-out was created.
creation_time: number; // milliseconds since the Unix epoch
// Time when the cash-out was confirmed via its TAN.
// Missing or null, when the operation wasn't confirmed yet.
confirmation_time?: number | null; // milliseconds since the Unix epoch
}
enum CashoutStatus {
// The payment was initiated after a valid
// TAN was received by the bank.
CONFIRMED = "confirmed",
// The cashout was created and now waits
// for the TAN by the author.
PENDING = "pending",
}
Response:
$cashoutId
being an aborted
operation.Every endpoint is served under /demobanks/default/access-api
.
See Taler Bank Access API. This API allows users to access
their bank accounts and trigger Taler withdrawals.
Every endpoint is served under /demobanks/default/integration-api
.
See Taler Bank Integration API. This API handles the communication
with Taler wallets.
Served under /demobanks/default/taler-wire-gateway
. Currently,
only the admin/add-incoming endpoint
is implemented. This endpoint allows testing, but the rest of
this API does never involve the Sandbox.
POST
/demobanks/default/ebics/subscribers
¶Allows (only) the admin user to associate a bank account to a EBICS subscriber. If the latter does not exist, it is created.
Request:
interface SubscriberRequest {
// hostID
hostID: string;
// userID
userID: string;
// partnerID
partnerID: string;
// systemID, optional.
systemID: string;
// Label of the bank account to associate with
// this subscriber.
demobankAccountLabel: string;
}
Note
The following endpoints are not served under the /demobank/default
segment.
This was the first API offered by Sandbox. It is used in some test cases. One is hosted at the Wallet repository; other MAY as well exist.
Except of the main EBICS handler located at “/ebicsweb”, all the EBICS calls have to authenticate the ‘admin’ user via the HTTP basic auth scheme.
POST
/admin/ebics/hosts
¶Create a new EBICS host.
Request:
interface EbicsHostRequest {
// Ebics version.
hostID: string;
// Name of the host.
ebicsVersion: string;
}
GET
/admin/ebics/hosts
¶Shows the list of all the hosts in the system.
Response:
interface EbicsHostResponse {
// shows the host IDs that are active in the system.
// The Ebics version *is* missing, but it's still available
// via the HEV message.
ebicsHosts: string[];
}
POST
/admin/ebics/hosts/$hostID/rotate-keys
¶Overwrite the bank’s Ebics keys with random ones. This is entirely meant for tests (as the Sandbox itself is) and no backup will be produced along this operation.
POST
/admin/ebics/bank-accounts
¶Associates a new bank account to an existing subscriber.
Note
This call allows to create a bank account without any associated user profile! That makes the basic auth access to the financial data only possible for the admin.
Request:
interface BankAccountRequest {
// Ebics subscriber
subscriber: {
userID: string;
partnerID: string;
systemID: string;
};
// IBAN
iban: string;
// BIC
bic: string;
// human name
name: string;
// bank account label
label: string;
}
GET
/admin/ebics/subscribers
¶Shows the list of all the subscribers in the system.
Response:
interface SubscribersResponse {
subscribers: Subscriber[]
}
interface Subscriber {
// userID
userID: string;
// partnerID
partnerID: string;
// hostID
hostID: string;
// Label of the bank account
// associated with this Ebics subscriber.
demobankAccountLabel: string;
}
POST
/admin/ebics/subscribers
¶Create a new EBICS subscriber without associating a bank account to it. This call is deprecated. Follow this page for updates over the EBICS management REST design.
Request:
interface SubscriberRequestDeprecated {
// hostID
hostID: string;
// userID
userID: string;
// partnerID
partnerID: string;
// systemID, optional.
systemID: string;
}
The access to a particular bank account is granted either to the owner or to admin, via the HTTP basic auth scheme. A ‘owner’ is a registered customer, who is identified by a username. The registration of customers is offered via the Taler Bank Access API.
Note
The current version allows only one bank account per customer, where the bank account name (also called ‘label’) equals the owner’s username.
GET
/admin/bank-accounts
¶Give summary of all the bank accounts. Only admin allowed.
Response:
interface AdminBankAccount {
// IBAN
iban: string;
// BIC
bic: string;
// human name
name: string;
// bank account label
label: string;
}
GET
/admin/bank-accounts/$accountLabel
¶Give information about a bank account.
Response:
interface AdminBankAccountBalance {
// Balance in the $currency:$amount format.
balance: Amount;
// IBAN of the bank account identified by $accountLabel
iban: string;
// BIC of the bank account identified by $accountLabel
bic: string;
// Mentions $accountLabel
label: string;
}
POST
/admin/bank-accounts/$accountLabel
¶Create bank account. Existing users without a bank account can request too.
Request: AdminBankAccount
GET
/admin/bank-accounts/$accountLabel/transactions
¶Inform about all the transactions of one bank account.
Response:
interface AdminTransactions {
payments: AdminTransaction[];
}
interface AdminTransaction {
// Label of the bank account involved in this payment.
accountLabel: string;
// Creditor IBAN
creditorIban: string;
// Debtor IBAN
debtorIban: string;
// UID given by one financial institute to this payment.
// FIXME: clarify whether that can be also assigned by
// the other party's institution.
accountServicerReference: string;
// ID of the Pain.001 that initiated this payment.
paymentInformationId: string;
// Unstructured remittance information.
subject: string;
// Date of the payment in the HTTP header format.
date: string;
// The number amount as a string.
amount: string;
// BIC of the creditor IBAN.
creditorBic: string;
// Legal name of the creditor.
creditorName: string;
// BIC of the debtor IBAN.
debtorBic: string;
// Legal name of the debtor.
debtorName: string;
// Payment's currency
currency: string;
// Have values 'credit' or 'debit' relative
// to the requesting user.
creditDebitIndicator: string;
}
POST
/admin/bank-accounts/$accountLabel/generate-transactions
¶Generate one incoming and one outgoing transaction for the bank account
identified by $accountLabel
. Only admin allowed.
POST
/admin/bank-accounts/$accountLabel/simulate-incoming-transaction
¶Book one incoming transaction for $accountLabel. The debtor (not required to be in the same bank) information is taken from the request. Only admin allowed.
Request:
interface AdminSimulateTransaction {
// Debtor IBAN.
debtorIban: string;
// Debtor BIC.
debtorBic: string;
// Debtor name.
debtorName: string;
// Amount number (without currency) as a string.
amount: string;
// Payment subject.
subject: string;
}
POST
/admin/payments/camt
¶Return the last camt.053 document from the requesting account.
Request
interface CamtParams {
// label of the bank account being queried.
bankaccount: string;
// The Camt type to return. Only '53' is allowed
// at this moment.
type: number;
}
Response
The last Camt.053 document related to the bank account mentioned in the request body.
The JSON type coming along a non 2xx response is the following:
interface SandboxError {
error: SandboxErrorDetail;
}
interface SandboxErrorDetail {
// String enum classifying the error.
type: ErrorType;
// Human-readable error description.
description: string;
}
enum ErrorType {
/**
* This error can be related to a business operation,
* a non-existent object requested by the client, or
* even when the bank itself fails.
*/
SandboxError = "sandbox-error",
/**
* It is the error type thrown by helper functions
* from the Util library. Those are used by both
* Sandbox and Nexus, therefore the actual meaning
* must be carried by the error 'message' field.
*/
UtilError = "util-error"
}