taler-docs

Documentation for GNU Taler components, APIs and protocols
Log | Files | Refs | README | LICENSE

api-bank-wire.rst (25363B)


      1 ..
      2   This file is part of GNU TALER.
      3   Copyright (C) 2019-2025, 2026 Taler Systems SA
      4 
      5   TALER is free software; you can redistribute it and/or modify it under the
      6   terms of the GNU Affero General Public License as published by the Free Software
      7   Foundation; either version 2.1, or (at your option) any later version.
      8 
      9   TALER is distributed in the hope that it will be useful, but WITHOUT ANY
     10   WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
     11   A PARTICULAR PURPOSE.  See the GNU Affero General Public License for more details.
     12 
     13   You should have received a copy of the GNU Affero General Public License along with
     14   TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
     15 
     16 .. _taler-wire-gateway-http-api:
     17 
     18 ===========================
     19 Taler Wire Gateway HTTP API
     20 ===========================
     21 
     22 ---------------
     23 Version History
     24 ---------------
     25 
     26 * ``v4``: adds account API.
     27 * ``v5``: adds metadata and prepared transfer fields
     28 
     29 -----------------
     30 Configuration API
     31 -----------------
     32 
     33 This section describes the API offered by the Taler wire adapters. The API is
     34 used by the exchange to trigger transactions and query incoming transactions, as
     35 well as by the auditor to query incoming and outgoing transactions.
     36 
     37 This API is currently implemented by the Taler Demo Bank, as well as by
     38 LibEuFin.
     39 
     40 .. http:get:: /config
     41 
     42   Return the protocol version and configuration information about the bank.
     43   This specification corresponds to ``current`` protocol being version **5**.
     44 
     45   **Response:**
     46 
     47   :http:statuscode:`200 OK`:
     48     The adapter responds with a `WireConfig` object. This request should
     49     virtually always be successful.
     50 
     51   **Details:**
     52 
     53   .. ts:def:: WireConfig
     54 
     55     interface WireConfig {
     56       // Name of the API.
     57       name: "taler-wire-gateway";
     58 
     59       // libtool-style representation of the Bank protocol version, see
     60       // https://www.gnu.org/software/libtool/manual/html_node/Versioning.html#Versioning
     61       // The format is "current:revision:age".
     62       version: string;
     63 
     64       // Currency used by this gateway.
     65       currency: string;
     66 
     67       // URN of the implementation (needed to interpret 'revision' in version).
     68       // @since v0, may become mandatory in the future.
     69       implementation?: string;
     70 
     71       // Whether implementation support account existence check
     72       // @since **v4**
     73       support_account_check: boolean;
     74     }
     75 
     76 --------------
     77 Authentication
     78 --------------
     79 
     80 The bank library authenticates requests to the wire gateway via
     81 `HTTP basic auth <https://tools.ietf.org/html/rfc7617>`_.
     82 
     83 -------------------
     84 Making Transactions
     85 -------------------
     86 
     87 .. http:post:: /transfer
     88 
     89   Initiate a new wire transfer from the exchange's bank account, typically to a
     90   merchant.
     91 
     92   The exchange's bank account is not included in the request, but instead
     93   derived from the username in the ``Authorization`` header and/or the request
     94   base URL.
     95 
     96   To make the API idempotent, the client must include a nonce. Requests with
     97   the same nonce are rejected unless the request is the same.
     98 
     99   **Request:**
    100 
    101   .. ts:def:: TransferRequest
    102 
    103     interface TransferRequest {
    104       // Nonce to make the request idempotent.  Requests with the same
    105       // ``request_uid`` that differs in any of the other fields
    106       // are rejected.
    107       request_uid: HashCode;
    108 
    109       // Amount to transfer.
    110       amount: Amount;
    111 
    112       // Base URL of the exchange.  Shall be included by the bank gateway
    113       // in the appropriate section of the wire transfer details.
    114       exchange_base_url: string;
    115       
    116       // Optional additional metadata to be stored in the transaction.
    117       // Must match [a-zA-Z0-9-.:]{1, 40}
    118       // @since **v5**
    119       metadata?: string;
    120 
    121       // Wire transfer identifier chosen by the exchange,
    122       // used by the merchant to identify the Taler order(s)
    123       // associated with this wire transfer.
    124       wtid: ShortHashCode;
    125 
    126       // The recipient's account identifier as a full payto URI.
    127       credit_account: string;
    128     }
    129 
    130   **Response:**
    131 
    132   :http:statuscode:`200 OK`:
    133     The request has been correctly handled, so the funds have been transferred to
    134     the recipient's account.  The body is a `TransferResponse`.
    135   :http:statuscode:`400 Bad request`:
    136     Request malformed. The bank replies with an `ErrorDetail` object.
    137   :http:statuscode:`401 Unauthorized`:
    138     Authentication failed, likely the credentials are wrong.
    139   :http:statuscode:`404 Not found`:
    140     The endpoint is wrong or the user name is unknown. The bank replies with an `ErrorDetail` object.
    141   :http:statuscode:`409 Conflict`:
    142     * ``TALER_EC_BANK_TRANSFER_REQUEST_UID_REUSED``: an operation with the same ``request_uid`` but different details has been submitted before.
    143     * ``TALER_EC_BANK_TRANSFER_WTID_REUSED``: an operation with the same ``wtid`` but a different ``request_uid`` has been submitted before.
    144 
    145   **Details:**
    146 
    147   .. ts:def:: TransferResponse
    148 
    149     interface TransferResponse {
    150       // Timestamp that indicates when the wire transfer will be executed.
    151       // In cases where the wire transfer gateway is unable to know when
    152       // the wire transfer will be executed, the time at which the request
    153       // has been received and stored will be returned.
    154       // The purpose of this field is for debugging (humans trying to find
    155       // the transaction) as well as for taxation (determining which
    156       // time period a transaction belongs to).
    157       timestamp: Timestamp;
    158 
    159       // Opaque ID of the wire transfer initiation performed by the bank.
    160       // It is different from the /history endpoints row_id.
    161       row_id: SafeUint64;
    162     }
    163 
    164 .. http:get:: /transfers
    165 
    166   Return a list of transfers initiated from the exchange.
    167 
    168   The bank account of the exchange is determined via the base URL and/or the
    169   user name in the ``Authorization`` header. The transfer history
    170   might come from a "virtual" account, where multiple real bank accounts are
    171   merged into one history.
    172 
    173   Since protocol **v3**.
    174 
    175   **Request:**
    176 
    177   :query limit: *Optional.*
    178     At most return the given number of results. Negative for descending by
    179     ``row_id``, positive for ascending by ``row_id``. Defaults to ``-20``.
    180   :query offset: *Optional.*
    181     Starting ``row_id`` for :ref:`pagination <row-id-pagination>`.
    182   :query status: *Optional*.
    183     Filters by status.
    184 
    185   **Response:**
    186 
    187   :http:statuscode:`200 OK`:
    188     JSON object of type `TransferList`.
    189   :http:statuscode:`204 No content`:
    190     There are no transfers to report (under the given filter).
    191   :http:statuscode:`400 Bad request`:
    192     Request malformed.
    193   :http:statuscode:`401 Unauthorized`:
    194     Authentication failed, likely the credentials are wrong.
    195   :http:statuscode:`404 Not found`:
    196     The endpoint is wrong or the user name is unknown.
    197 
    198   **Details:**
    199 
    200   .. ts:def:: TransferList
    201 
    202     interface TransferList {
    203       // Array of initiated transfers.
    204       transfers: TransferListStatus[];
    205 
    206       // Full payto:// URI to identify the sender of funds.
    207       // This must be one of the exchange's bank accounts.
    208       // Credit account is shared by all incoming transactions
    209       // as per the nature of the request.
    210       debit_account: string;
    211     }
    212 
    213   .. ts:def:: TransferListStatus
    214 
    215     interface TransferListStatus {
    216       // Opaque ID of the wire transfer initiation performed by the bank.
    217       // It is different from the /history endpoints row_id.
    218       row_id: SafeUint64;
    219 
    220       // Current status of the transfer
    221       // pending: the transfer is in progress
    222       // transient_failure: the transfer has failed but may succeed later
    223       // permanent_failure: the transfer has failed permanently and will never appear in the outgoing history
    224       // success: the transfer has succeeded  and appears in the outgoing history
    225       status: "pending" | "transient_failure" | "permanent_failure" | "success";
    226 
    227       // Amount to transfer.
    228       amount: Amount;
    229 
    230       // The recipient's account identifier as a full payto:// URI.
    231       credit_account: string;
    232 
    233       // Timestamp that indicates when the wire transfer was executed.
    234       // In cases where the wire transfer gateway is unable to know when
    235       // the wire transfer will be executed, the time at which the request
    236       // has been received and stored will be returned.
    237       // The purpose of this field is for debugging (humans trying to find
    238       // the transaction) as well as for taxation (determining which
    239       // time period a transaction belongs to).
    240       timestamp: Timestamp;
    241     }
    242 
    243 
    244 .. http:get:: /transfers/$ROW_ID
    245 
    246   Return the status of a transfer initiated from the exchange, identified by the ``ROW_ID``.
    247 
    248   Since protocol **v3**.
    249 
    250   **Response:**
    251 
    252   :http:statuscode:`200 OK`:
    253     The transfer is known, and details are given in the `TransferStatus` response body.
    254   :http:statuscode:`400 Bad request`:
    255     Request malformed.
    256   :http:statuscode:`401 Unauthorized`:
    257     Authentication failed, likely the credentials are wrong.
    258   :http:statuscode:`404 Not found`:
    259     The transfer was not found.
    260 
    261   **Details:**
    262 
    263   .. ts:def:: TransferStatus
    264 
    265     interface TransferStatus {
    266       // Current status of the transfer
    267       // pending: the transfer is in progress
    268       // transient_failure: the transfer has failed but may succeed later
    269       // permanent_failure: the transfer has failed permanently and will never appear in the outgoing history
    270       // success: the transfer has succeeded  and appears in the outgoing history
    271       status: "pending" | "transient_failure" | "permanent_failure" | "success";
    272 
    273       // Optional unstructured messages about the transfer's status. Can be used to document the reasons for failure or the state of progress.
    274       status_msg?: string;
    275 
    276       // Amount to transfer.
    277       amount: Amount;
    278 
    279       // Base URL of the exchange.  Shall be included by the bank gateway
    280       // in the appropriate section of the wire transfer details.
    281       exchange_base_url: string;
    282       
    283       // Optional additional metadata to be stored in the transaction.
    284       // @since **v5**
    285       metadata?: string;
    286 
    287       // Wire transfer identifier chosen by the exchange,
    288       // used by the merchant to identify the Taler order(s)
    289       // associated with this wire transfer.
    290       wtid: ShortHashCode;
    291 
    292       // The recipient's account identifier as a full payto URI.
    293       credit_account: string;
    294 
    295       // Timestamp that indicates when the wire transfer was executed.
    296       // In cases where the wire transfer gateway is unable to know when
    297       // the wire transfer will be executed, the time at which the request
    298       // has been received and stored will be returned.
    299       // The purpose of this field is for debugging (humans trying to find
    300       // the transaction) as well as for taxation (determining which
    301       // time period a transaction belongs to).
    302       timestamp: Timestamp;
    303     }
    304 
    305 --------------------------------
    306 Querying the transaction history
    307 --------------------------------
    308 
    309 The exchange's bank account is derived from the username in the
    310 ``Authorization`` header and/or the request's base URL. In fact, the
    311 transaction history may come from a "virtual" account, where several real bank
    312 accounts are merged into a single history.
    313 
    314 .. http:get:: /history/incoming
    315 
    316   Return a list of transactions made from or to the exchange.
    317 
    318   Incoming transactions must contain a valid reserve public key.  If a bank
    319   transaction does not conform to the right syntax, the wire gateway must not
    320   report it to the exchange, and send funds back to the sender if possible.
    321 
    322   **Request:**
    323 
    324   :query limit: *Optional.*
    325     At most return the given number of results. Negative for descending by ``row_id``, positive for ascending by ``row_id``. Defaults to ``-20``. Since protocol **v2**.
    326   :query offset: *Optional.*
    327     Starting ``row_id`` for :ref:`pagination <row-id-pagination>`. Since protocol **v2**.
    328   :query timeout_ms: *Optional.*
    329     Timeout in milliseconds, for :ref:`long-polling <long-polling>`, to wait for at least one element to be shown. Only useful if *limit* is positive. Since protocol **v2**.
    330   :query delta: *Optional.*
    331     Deprecated in protocol **v2**. Use *limit* instead.
    332   :query start: *Optional.*
    333     Deprecated in protocol **v2**. Use *offset* instead.
    334   :query long_poll_ms: *Optional.*
    335     Deprecated in protocol **v2**. Use *timeout_ms* instead.
    336 
    337   **Response:**
    338 
    339   :http:statuscode:`200 OK`:
    340      JSON object of type `IncomingHistory`.
    341   :http:statuscode:`204 No content`:
    342     There are not transactions to report (under the given filter).
    343   :http:statuscode:`400 Bad request`:
    344      Request malformed. The bank replies with an `ErrorDetail` object.
    345   :http:statuscode:`401 Unauthorized`:
    346      Authentication failed, likely the credentials are wrong.
    347   :http:statuscode:`404 Not found`:
    348      The endpoint is wrong or the user name is unknown. The bank replies with an `ErrorDetail` object.
    349 
    350   **Details:**
    351 
    352   .. ts:def:: IncomingHistory
    353 
    354     interface IncomingHistory {
    355       // Array of incoming transactions.
    356       incoming_transactions: IncomingBankTransaction[];
    357 
    358       // Full payto URI to identify the receiver of funds.
    359       // This must be one of the exchange's bank accounts.
    360       // Credit account is shared by all incoming transactions
    361       // as per the nature of the request.
    362       credit_account: string;
    363     }
    364 
    365   .. ts:def:: IncomingBankTransaction
    366 
    367     // Union discriminated by the "type" field.
    368     type IncomingBankTransaction =
    369     | IncomingKycAuthTransaction
    370     | IncomingReserveTransaction
    371     | IncomingWadTransaction;
    372 
    373   .. ts:def:: IncomingKycAuthTransaction
    374 
    375     // Since protocol **v1**.
    376     interface IncomingKycAuthTransaction {
    377       type: "KYCAUTH";
    378 
    379       // Opaque identifier of the returned record.
    380       row_id: SafeUint64;
    381 
    382       // Date of the transaction.
    383       date: Timestamp;
    384 
    385       // Amount received before credit_fee.
    386       amount: Amount;
    387 
    388       // Fee paid by the creditor.
    389       // If not null, creditor actually received amount - credit_fee
    390       // @since **v3**
    391       credit_fee?: Amount;
    392 
    393       // Full payto URI to identify the sender of funds.
    394       debit_account: string;
    395 
    396       // The account public key extracted from the transaction details.
    397       account_pub: EddsaPublicKey;
    398 
    399       // The authorization public key used for mapping
    400       // @since **v5**
    401       authorization_pub?: EddsaPublicKey;
    402 
    403       // Signature of the account public key using the authorization private key
    404       // @since **v5**
    405       authorization_sig?: EddsaSignature;
    406     }
    407 
    408   .. ts:def:: IncomingReserveTransaction
    409 
    410     interface IncomingReserveTransaction {
    411       type: "RESERVE";
    412 
    413       // Opaque identifier of the returned record.
    414       row_id: SafeUint64;
    415 
    416       // Date of the transaction.
    417       date: Timestamp;
    418 
    419       // Amount received before credit_fee.
    420       amount: Amount;
    421 
    422       // Fee payed by the creditor.
    423       // If not null, creditor actually received amount - 
    424       // @since **v3**
    425       credit_fee?: Amount;
    426 
    427       // Full payto URI to identify the sender of funds.
    428       debit_account: string;
    429 
    430       // The reserve public key extracted from the transaction details.
    431       reserve_pub: EddsaPublicKey;
    432 
    433       // The authorization public key used for mapping
    434       // @since **v5**
    435       authorization_pub?: EddsaPublicKey;
    436 
    437       // Signature of the reserve public key using the authorization private key
    438       // @since **v5**
    439       authorization_sig?: EddsaSignature;
    440     }
    441 
    442   .. ts:def:: IncomingWadTransaction
    443 
    444     interface IncomingWadTransaction {
    445       type: "WAD";
    446 
    447       // Opaque identifier of the returned record.
    448       row_id: SafeUint64;
    449 
    450       // Date of the transaction.
    451       date: Timestamp;
    452 
    453       // Amount received before credit_fee.
    454       amount: Amount;
    455 
    456       // Fee payed by the creditor.
    457       // If not null, creditor actually received amount - credit_fee
    458       // @since **v3**
    459       credit_fee?: Amount;
    460 
    461       // Full payto URI to identify the sender of funds.
    462       debit_account: string;
    463 
    464       // Base URL of the exchange that originated the wad.
    465       origin_exchange_url: string;
    466 
    467       // The reserve public key extracted from the transaction details.
    468       wad_id: WadId;
    469     }
    470 
    471 
    472 .. http:get:: /history/outgoing
    473 
    474   Return a list of transactions made by the exchange, typically to a merchant.
    475 
    476   **Request:**
    477 
    478   :query limit: *Optional.*
    479     At most return the given number of results. Negative for descending by ``row_id``, positive for ascending by ``row_id``. Defaults to ``-20``. Since protocol **v2**.
    480   :query offset: *Optional.*
    481     Starting ``row_id`` for :ref:`pagination <row-id-pagination>`. Since protocol **v2**.
    482   :query timeout_ms: *Optional.*
    483     Timeout in milliseconds, for :ref:`long-polling <long-polling>`, to wait for at least one element to be shown. Only useful if *limit* is positive. Since protocol **v2**.
    484   :query delta: *Optional.*
    485     Deprecated in protocol **v2**. Use *limit* instead.
    486   :query start: *Optional.*
    487     Deprecated in protocol **v2**. Use *offset* instead.
    488   :query long_poll_ms: *Optional.*
    489     Deprecated in protocol **v2**. Use *timeout_ms* instead.
    490 
    491   **Response:**
    492 
    493   :http:statuscode:`200 OK`:
    494     JSON object of type `OutgoingHistory`.
    495   :http:statuscode:`204 No content`:
    496     There are not transactions to report (under the given filter).
    497   :http:statuscode:`400 Bad request`:
    498     Request malformed. The bank replies with an `ErrorDetail` object.
    499   :http:statuscode:`401 Unauthorized`:
    500     Authentication failed, likely the credentials are wrong.
    501   :http:statuscode:`404 Not found`:
    502     The endpoint is wrong or the user name is unknown. The bank replies with an `ErrorDetail` object.
    503 
    504   **Details:**
    505 
    506   .. ts:def:: OutgoingHistory
    507 
    508     interface OutgoingHistory {
    509       // Array of outgoing transactions.
    510       outgoing_transactions: OutgoingBankTransaction[];
    511 
    512       // Full payto URI to identify the sender of funds.
    513       // This must be one of the exchange's bank accounts.
    514       // Credit account is shared by all incoming transactions
    515       // as per the nature of the request.
    516       debit_account: string;
    517     }
    518 
    519   .. ts:def:: OutgoingBankTransaction
    520 
    521     interface OutgoingBankTransaction {
    522       // Opaque identifier of the returned record.
    523       row_id: SafeUint64;
    524 
    525       // Date of the transaction.
    526       date: Timestamp;
    527 
    528       // Amount transferred.
    529       amount: Amount;
    530 
    531       // Fee paid by the debtor.
    532       // If not null, debtor actually paid amount + debit_fee
    533       // @since **v3**
    534       debit_fee?: Amount;
    535 
    536       // Full payto URI to identify the receiver of funds.
    537       credit_account: string;
    538 
    539       // The wire transfer ID in the outgoing transaction.
    540       wtid: ShortHashCode;
    541 
    542       // Base URL of the exchange.
    543       exchange_base_url: string;
    544       
    545       // Optional additional metadata.
    546       // @since **v5**
    547       metadata?: string;
    548     }
    549 
    550 
    551 -----------------
    552 Wire Account APIs
    553 -----------------
    554 
    555 .. http:get:: /account/check
    556 
    557   Check account existence.
    558 
    559   Since protocol **v4**.
    560 
    561   **Request:**
    562 
    563   :query account:
    564     Payto URI of the account.
    565 
    566   **Response:**
    567 
    568   :http:statuscode:`200 OK`:
    569     JSON object of type `AccountInfo`.
    570   :http:statuscode:`400 Bad request`:
    571     Request malformed. The bank replies with an `ErrorDetail` object.
    572   :http:statuscode:`401 Unauthorized`:
    573     Authentication failed, likely the credentials are wrong.
    574   :http:statuscode:`404 Not found`:
    575     * ``TALER_EC_BANK_UNKNOWN_ACCOUNT``: unknown account.
    576   :http:statuscode:`501 Not Implemented`:
    577     This server does not support account check.
    578 
    579   **Details:**
    580 
    581   .. ts:def:: AccountInfo
    582 
    583     interface AccountInfo {
    584     }
    585 
    586 -----------------------
    587 Wire Transfer Test APIs
    588 -----------------------
    589 
    590 Endpoints in this section are only used for integration tests and never
    591 exposed by bank gateways in production.
    592 
    593 .. _twg-admin-add-incoming:
    594 
    595 .. http:post:: /admin/add-incoming
    596 
    597   Simulate a transfer from a customer to the exchange.  This API is *not*
    598   idempotent since it's only used in testing.
    599 
    600   **Request:**
    601 
    602   .. ts:def:: AddIncomingRequest
    603 
    604     interface AddIncomingRequest {
    605       // Amount to transfer.
    606       amount: Amount;
    607 
    608       // Reserve public key that is included in the wire transfer details
    609       // to identify the reserve that is being topped up.
    610       reserve_pub: EddsaPublicKey;
    611 
    612       // Account (as full payto URI) that makes the wire transfer to the exchange.
    613       // Usually this account must be created by the test harness before this
    614       // API is used. An exception is the "fakebank", where any debit account
    615       // can be specified, as it is automatically created.
    616       debit_account: string;
    617     }
    618 
    619   **Response:**
    620 
    621   :http:statuscode:`200 OK`:
    622     The request has been correctly handled, so the funds have been transferred to
    623     the recipient's account.  The body is a `AddIncomingResponse`.
    624   :http:statuscode:`400 Bad request`:
    625     The request is malformed. The bank replies with an `ErrorDetail` object.
    626   :http:statuscode:`401 Unauthorized`:
    627     Authentication failed, likely the credentials are wrong.
    628   :http:statuscode:`404 Not found`:
    629     The endpoint is wrong or the user name is unknown. The bank replies with an `ErrorDetail` object.
    630   :http:statuscode:`409 Conflict`:
    631     The 'reserve_pub' argument was used previously in another transfer, and the specification mandates that reserve public keys must not be reused.
    632 
    633   **Details:**
    634 
    635   .. ts:def:: AddIncomingResponse
    636 
    637     interface AddIncomingResponse {
    638       // Timestamp that indicates when the wire transfer will be executed.
    639       // In cases where the wire transfer gateway is unable to know when
    640       // the wire transfer will be executed, the time at which the request
    641       // has been received and stored will be returned.
    642       // The purpose of this field is for debugging (humans trying to find
    643       // the transaction) as well as for taxation (determining which
    644       // time period a transaction belongs to).
    645       timestamp: Timestamp;
    646 
    647       // Opaque ID of the wire transfer initiation performed by the bank.
    648       // It is different from the /history endpoints row_id.
    649       row_id: SafeUint64;
    650     }
    651 
    652 
    653 
    654 .. _twg-admin-add-kycauth:
    655 
    656 .. http:post:: /admin/add-kycauth
    657 
    658   Simulate a transfer from a customer to the exchange.  This API is *not*
    659   idempotent since it's only used in testing.
    660 
    661   **Request:**
    662 
    663   .. ts:def:: AddKycauthRequest
    664 
    665     interface AddKycauthRequest {
    666       // Amount to transfer.
    667       amount: Amount;
    668 
    669       // Account public key that is included in the wire transfer details
    670       // to associate this key with the originating bank account.
    671       account_pub: EddsaPublicKey;
    672 
    673       // Account (as full payto URI) that makes the wire transfer to the exchange.
    674       // Usually this account must be created by the test harness before this
    675       // API is used. An exception is the "fakebank", where any debit account
    676       // can be specified, as it is automatically created.
    677       debit_account: string;
    678     }
    679 
    680   **Response:**
    681 
    682   :http:statuscode:`200 OK`:
    683     The request has been correctly handled, so the funds have been transferred to
    684     the recipient's account.  The body is a `AddIncomingResponse`.
    685   :http:statuscode:`400 Bad request`:
    686     The request is malformed. The bank replies with an `ErrorDetail` object.
    687   :http:statuscode:`401 Unauthorized`:
    688     Authentication failed, likely the credentials are wrong.
    689   :http:statuscode:`404 Not found`:
    690     The endpoint is wrong or the user name is unknown. The bank replies with an `ErrorDetail` object.
    691 
    692 .. http:post:: /admin/add-mapped
    693 
    694   Simulate a transfer from a customer to the exchange.  This API is *not*
    695   idempotent since it's only used in testing.
    696 
    697   Since protocol **v5**.
    698 
    699   **Request:**
    700 
    701   .. ts:def:: AddMappedRequest
    702 
    703     interface AddMappedRequest {
    704       // Amount to transfer.
    705       amount: Amount;
    706 
    707       // Authorization public key used for registration.
    708       authorization_pub: EddsaPublicKey;
    709 
    710       // Account (as full payto URI) that makes the wire transfer to the exchange.
    711       // Usually this account must be created by the test harness before this
    712       // API is used. An exception is the "fakebank", where any debit account
    713       // can be specified, as it is automatically created.
    714       debit_account: string;
    715     }
    716 
    717   **Response:**
    718 
    719   :http:statuscode:`200 OK`:
    720     The request has been correctly handled, so the funds have been transferred to
    721     the recipient's account.  The body is a `AddIncomingResponse`.
    722   :http:statuscode:`400 Bad request`:
    723     The request is malformed. The bank replies with an `ErrorDetail` object.
    724   :http:statuscode:`401 Unauthorized`:
    725     Authentication failed, likely the credentials are wrong.
    726   :http:statuscode:`404 Not found`:
    727     The endpoint is wrong or the user name is unknown. The bank replies with an `ErrorDetail` object.
    728   :http:statuscode:`409 Conflict`:
    729     The 'authorization_pub' argument is unknown or have already been used for a non recurrent transfer.
    730 
    731 
    732 Security Considerations
    733 =======================
    734 
    735 For implementors:
    736 
    737 * The withdrawal operation ID must contain enough entropy to be unguessable.
    738 
    739 Design:
    740 
    741 * The user must complete the 2FA step of the withdrawal in the context of their banking
    742   app or online banking Website.
    743   We explicitly reject any design where the user would have to enter a confirmation code
    744   they get from their bank in the context of the wallet, as this would teach and normalize
    745   bad security habits.