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.