..
This file is part of GNU TALER.
Copyright (C) 2014-2020 Taler Systems SA
TALER is free software; you can redistribute it and/or modify it under the
terms of the GNU Affero General Public License as published by the Free Software
Foundation; either version 2.1, or (at your option) any later version.
TALER is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License along with
TALER; see the file COPYING. If not, see
.. target audience: developer, core developer
.. _sandbox-api:
Sandbox API
###########
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.
Demobanks
=========
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.
.. _circuit-api:
Circuit API
^^^^^^^^^^^
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
:doc:`CLI <../libeufin/circuit-cli-commands>` and the SPA. The SPA is generally served
along Sandbox ``/`` path.
Accounts
--------
.. _circuit-register:
.. http:post:: /accounts
Create a new bank account. Only the administrator is allowed.
**Request:**
.. ts:def:: CircuitAccountRequest
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;
}
.. ts:def:: CircuitContactData
interface CircuitContactData {
// E-Mail address
email?: EmailAddress;
// Phone number.
phone?: PhoneNumber;
}
**Response:**
:http:statuscode:`204 No content`:
The account was successfully created.
:http:statuscode:`400 Bad request`:
Input data was invalid. For example, the client specified a invalid
phone number or e-mail address.
:http:statuscode:`403 Forbidden`:
The response should indicate one of the following reasons.
* A institutional username was attempted, like ``admin`` or ``bank``.
* A non admin user tried the operation.
:http:statuscode:`409 Conflict`:
At least one registration detail was not available,
the error message should inform about it.
.. _circuit-delete-account:
.. http: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:**
:http:statuscode:`204 No content`:
The account was successfully deleted.
:http:statuscode:`403 Forbidden`:
The administrator specified a institutional username, like
``admin`` or ``bank``.
:http:statuscode:`404 Not found`:
The username was not found.
:http:statuscode:`412 Precondition failed`:
The balance was not zero.
.. _circuit-reconfig:
.. http: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:**
.. ts:def:: CircuitAccountReconfiguration
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:**
:http:statuscode:`204 No content`:
Operation successful.
:http:statuscode:`403 Forbidden`:
The rights to change ``$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.
:http:statuscode:`404 Not found`:
The account pointed by ``$username``
was not found.
.. _circuit-password-reconfig:
.. http:patch:: /accounts/$username/auth
Allows administrators *and* ordinary users to
change the account's password.
**Request:**
.. ts:def:: AccountPasswordChange
interface AccountPasswordChange {
// New password.
new_password: string;
}
**Response:**
:http:statuscode:`204 No content`:
Operation successful.
.. _circuit-account-list:
.. http: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 :doc:`Access API `
offers that. This request is only available to the administrator.
**Request:**
:query filter: *Optional.*
Pattern to filter on the account legal name. Given
the filter 'foo', all the results will **contain**
'foo' in their legal name. Without this option,
all the existing accounts are returned.
**Response:**
`CircuitAccounts `_
.. _circuit-accounts:
.. ts:def:: CircuitAccounts
interfaces CircuitAccounts {
customers: CircuitAccountMinimalData[];
}
.. ts:def:: Balance
interface Balance {
amount: Amount;
credit_debit_indicator: "credit" | "debit";
}
.. ts:def:: CircuitAccountMinimalData
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;
}
:http:statuscode:`200 OK`:
At least one account was found.
:http:statuscode:`204 No Content`:
No accounts were found for the given request.
:http:statuscode:`403 Forbidden`:
A ordinary user invoked this call.
.. _circuit-account-info:
.. http:get:: /accounts/$username
Obtains information relative to the account owned by
``$username``. The request is available to the administrator
and ``$username`` itself.
**Response:**
.. ts:def:: CircuitAccountData
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;
}
:http:statuscode:`403 Forbidden`:
The user is not allowed.
Cashouts
--------
.. _circuit-cashout:
.. http: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:**
`CashoutRequest `_
.. ts:def:: TanChannel
enum TanChannel {
SMS = "sms",
EMAIL = "email",
FILE = "file"
}
.. _cashout-request:
.. ts:def:: CashoutRequest
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:**
.. ts:def:: CashoutPending
interface CashoutPending {
// UUID identifying the operation being created
// and now waiting for the TAN confirmation.
uuid: string;
}
:http:statuscode:`202 Accepted`:
The cashout request was correctly created and
the TAN authentication now is pending.
:http:statuscode:`400 Bad request`:
The exchange rate was incorrectly applied.
:http:statuscode:`403 Forbidden`:
A institutional user (``admin`` or ``bank``) tried the operation.
:http:statuscode:`409 Conflict`:
The user did not share any contact data where to send the TAN.
:http:statuscode:`412 Precondition failed`:
The account does not have sufficient funds.
:http:statuscode:`503 Service unavailable`:
The bank does not support the TAN channel for this operation.
.. _circuit-cashout-abort:
.. http:post:: /cashouts/$cashoutId/abort
Aborts the ``$cashoutId`` operation. Original author
*and* admin are both allowed.
**Response:**
:http:statuscode:`204 No content`:
``$cashoutId`` was found in the *pending* state
and got successfully aborted.
:http:statuscode:`404 Not found`:
``$cashoutId`` is not found. Note: that happens
also when ``$cashoutId`` got aborted before this request.
:http:statuscode:`412 Precondition failed`:
``$cashoutId`` was already confirmed.
.. _circuit-cashout-confirm:
.. http: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:**
.. ts:def:: CashoutConfirm
interface CashoutConfirm {
// the TAN that confirms $cashoutId.
tan: string;
}
**Response:**
:http:statuscode:`204 No content`:
``$cashoutId`` was found in the *pending* state and
got successfully confirmed.
:http:statuscode:`403 Forbidden`:
wrong TAN.
:http:statuscode:`404 Not found`:
``$cashoutId`` is not found. Note: that happens
also when ``$cashoutId`` got aborted before this request.
:http:statuscode:`409 Conflict`:
At least the following two cases are possible
* an institutional user (``admin`` or ``bank``) tried the operation
* the user changed their cash-out address between the creation and the confirmation of ``$cashoutId``.
:http:statuscode:`412 Precondition failed`:
``$cashoutId`` was already confirmed.
.. http: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:**
:query amount_debit: this is the amount that the user will get
deducted from their regional bank account.
:query amount_credit: this is the amount that the user will receive
in their fiat bank account.
**Response:**
.. ts:def:: CashoutEstimate
interface CashoutEstimate {
// Amount that the user will get deducted from their regional
// bank account, according to the 'amount_credit' value.
amount_debit: Amount;
// Amount that the user will receive in their fiat
// bank account, according to 'amount_debit'.
amount_credit: Amount;
}
:http:statuscode:`200 Ok`:
Response contains the calculated values
:http:statuscode:`400 Bad request`:
Both parameters have been provided and the calculation is not correct,
or none of them has been provided.
.. _cashout-rates:
.. http:get:: /config
**Response:**
.. ts:def:: Config
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;
}
.. ts:def:: RatiosAndFees
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.
.. _circuit-cashouts:
.. http: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:**
:query account: *Optional.*
Filters the request to only get the cash-out operations related to
the account specified in this parameter. Ordinary users must use
this option and pass their own username as the value.
**Response:**
.. ts:def:: Cashouts
interface Cashouts {
// Every string represents a cash-out operation UUID.
cashouts: string[];
}
:http:statuscode:`200 OK`:
At least one cash-out operation was found.
:http:statuscode:`204 No Content`:
No cash-out operations were found at the bank
:http:statuscode:`403 Forbidden`:
A ordinary user invoked this call either without
the ``account`` parameter or by passing to it someone
else's username.
.. _circuit-cashout-details:
.. http:get:: /cashouts/$cashoutId
Informs about the status of the ``$cashoutId`` operation.
The request is available to the administrator and the original author.
**Response:**
`CashoutStatusResponse `_
.. _cashout-status:
.. ts:def:: CashoutStatus
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
}
.. ts:def:: CashoutStatus
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:**
:http:statuscode:`404 Not found`:
The cashout operation was not found. That is
*also* the case of ``$cashoutId`` being an aborted
operation.
Access API
^^^^^^^^^^
Every endpoint is served under ``/demobanks/default/access-api``.
See :doc:`/core/api-bank-access`. This API allows users to access
their bank accounts and trigger Taler withdrawals.
Integration API
^^^^^^^^^^^^^^^
Every endpoint is served under ``/demobanks/default/integration-api``.
See :doc:`/core/api-bank-integration`. This API handles the communication
with Taler wallets.
Taler Wire Gateway API
^^^^^^^^^^^^^^^^^^^^^^
Served under ``/demobanks/default/taler-wire-gateway``. Currently,
only the :ref:`admin/add-incoming ` endpoint
is implemented. This endpoint allows testing, but the rest of
this API does never involve the Sandbox.
EBICS API
^^^^^^^^^
.. _demobank-create-ebics-subscriber:
.. http: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:**
.. ts:def:: SubscriberRequest
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.
Legacy API
==========
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.
EBICS Hosts
^^^^^^^^^^^
.. http:post:: /admin/ebics/hosts
Create a new EBICS host.
**Request:**
.. ts:def:: EbicsHostRequest
interface EbicsHostRequest {
// Ebics version.
hostID: string;
// Name of the host.
ebicsVersion: string;
}
.. http:get:: /admin/ebics/hosts
Shows the list of all the hosts in the system.
**Response:**
.. ts:def:: EbicsHostResponse
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[];
}
.. http: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.
EBICS Subscribers
^^^^^^^^^^^^^^^^^
.. http: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:**
.. ts:def:: BankAccountRequest
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;
}
.. http:get:: /admin/ebics/subscribers
Shows the list of all the subscribers in the system.
**Response:**
.. ts:def:: SubscribersResponse
interface SubscribersResponse {
subscribers: Subscriber[]
}
.. ts:def:: Subscriber
interface Subscriber {
// userID
userID: string;
// partnerID
partnerID: string;
// hostID
hostID: string;
// Label of the bank account
// associated with this Ebics subscriber.
demobankAccountLabel: string;
}
.. http: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:**
.. ts:def:: SubscriberRequestDeprecated
interface SubscriberRequestDeprecated {
// hostID
hostID: string;
// userID
userID: string;
// partnerID
partnerID: string;
// systemID, optional.
systemID: string;
}
Bank accounts
^^^^^^^^^^^^^
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 :doc:`/core/api-bank-access`.
.. note::
The current version allows only one bank account per
customer, where the bank account name (also called 'label')
equals the owner's username.
.. http:get:: /admin/bank-accounts
Give summary of all the bank accounts. Only admin allowed.
**Response:**
.. ts:def:: AdminBankAccount
interface AdminBankAccount {
// IBAN
iban: string;
// BIC
bic: string;
// human name
name: string;
// bank account label
label: string;
}
.. http:get:: /admin/bank-accounts/$accountLabel
Give information about a bank account.
**Response:**
.. ts:def:: AdminBankAccountBalance
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;
}
.. http:post:: /admin/bank-accounts/$accountLabel
Create bank account. Existing users without a bank account
can request too.
**Request:** :ts:type:`AdminBankAccount`
Transactions
^^^^^^^^^^^^
.. http:get:: /admin/bank-accounts/$accountLabel/transactions
Inform about all the transactions of one bank account.
**Response:**
.. ts:def:: AdminTransactions
interface AdminTransactions {
payments: AdminTransaction[];
}
.. ts:def:: 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;
}
.. http:post:: /admin/bank-accounts/$accountLabel/generate-transactions
Generate one incoming and one outgoing transaction for the bank account
identified by ``$accountLabel``. Only admin allowed.
.. http: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:**
.. ts:def:: AdminSimulateTransaction
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;
}
.. http:post:: /admin/payments/camt
Return the last camt.053 document from the requesting account.
**Request**
.. code-block:: tsref
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.
.. _error-types:
Errors
======
The JSON type coming along a non 2xx response is the following:
.. ts:def:: SandboxError
interface SandboxError {
error: SandboxErrorDetail;
}
.. ts:def:: SandboxErrorDetail
interface SandboxErrorDetail {
// String enum classifying the error.
type: ErrorType;
// Human-readable error description.
description: string;
}
.. ts:def:: ErrorType
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"
}