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