Skip to main content

Typed Python client library for the API of the bank Wise

Project description

wise-banking-api-client

PyPI Package Python Versions GitHub Workflow Status Downloads Code Style: Black

An unofficial Python client library for the Wise API.

The classes, functions and interfaces that this library provides are very much in-development and prone to change.

Ecosystem:

  • pywisetransfer is a slim and easy-to-understand client library for the Wise API.
  • This is a fork of pywisetransfer to enhance functionality. It is built with pydantic to test completeness, validate responses and requests to make sure API changes and inconsistencies in the official documentation are detected. The focus is on ease of use, documentation and features to support developers make less mistakes and write code faster.

Installation

You can install this package from PyPI:

pip install wise-banking-api-client

Command Line

You can use some of the functions on the command line. By installing wise_banking_api_client[cli], you also install the wise command.

pip install wise_banking_api_client[cli]

Help and Information

Get help:

wise --help

Generate a Key Pair

We can generate a key pair. Note that this needs the openssl command installed.

$ wise new-key
writing RSA key
private key: wise.com.private.pem
public key: wise.com.public.pem
You can now upload the public key to https://wise.com or https://sandbox.transferwise.tech.

Check API Key

You can check if your API key and private key work.

$ WISE_API_KEY="your api key" wise check
Permissions on sandbox: read+write+sca
Permissions on live: none

Python Package

After installation, you should be able to import the package.

>>> from wise_banking_api_client import Client

Wise provides two APIs: live and sandbox. You can use either of these environments with this library.

API Key

In order to use the API, you need to obtain an API key. This key looks like this: 12345678-1234-1234-1234-123456789abcde

To use the sandbox API, log into sandbox.transferwise.tech. Then, go to Settings 🠚 Developer Tools 🠚 API-Tokens. Create and copy a new API key.

To use your own account, log into wise.com. Then click on your account at the top right 🠚 Integrations and Tools 🠚 Developer Tools 🠚 API Tokens and add a new token.

API Requests

The API requests are made using the requests library. First of all, you create a Client object:

  • Create a Client object with your API key for the live environment:

    >>> client = Client(api_key="your-api-key-here", environment="live")
    
  • Create a Client object with your API key for the sandbox environment:

    >>> client = Client(api_key="your-api-key-here")
    
  • Create a Client object which interacts with the recorded API which is used for the tests:

    >>> from wise_banking_api_client.test import TestClient
    >>> client = TestClient()
    

    After this, all calls to the real Wise API are blocked by the responses library. To stop using the recorded API:

    client.stop()
    

Examples

This section provides a few examples of how to use this package.

Profiles

This library follows the Wise API Reference. So, if you find e.g. profile here, you can look up all the values of it in the Wise API Reference.

If you create a sandbox account, you should have two profiles: business and personal.

>>> for profile in client.profiles.list():
...     print(f"id: {profile.id}")
...     print(f"type: {profile.type}")
...     print(f"name: {profile.details.name}")
... 
id: 28577318
type: personal
name: Teresa Adams
id: 28577319
type: business
name: Law and Daughters 6423

Receive Money

One profile can have several accounts in different currencies. This shows which currencies are accepted and how to receive money.

>>> for profile in client.profiles.list():
...     accounts = client.account_details.list(profile_id=profile.id)
...     print(f"type: {profile.type}")
...     for account in accounts:
...         print(
...             f"    currency: {account.currency.code}"
...             f" receive with: {', '.join(feature.title for feature in account.bankFeatures if feature.supported)}")
... 
type: personal
    currency: EUR receive with: Receive locally, Receive internationally (Swift), Set up Direct Debits, Receive from PayPal and Stripe
    currency: GBP receive with: Receive locally, Receive internationally (Swift), Set up Direct Debits, Receive from PayPal and Stripe
    currency: USD receive with: Receive locally, Receive internationally (Swift), Set up Direct Debits, Receive from PayPal and Stripe
    currency: AUD receive with: Receive locally, Set up Direct Debits
    currency: NZD receive with: Receive locally
    currency: CAD receive with: Receive locally, Set up Direct Debits
    currency: HUF receive with: Receive locally
    currency: MYR receive with:
    currency: RON receive with: Receive locally
    currency: SGD receive with: Receive locally
    currency: TRY receive with: Receive locally
type: business
    currency: EUR receive with: Receive locally, Receive internationally (Swift), Set up Direct Debits, Receive from PayPal and Stripe
    currency: GBP receive with: Receive locally, Receive internationally (Swift), Set up Direct Debits, Receive from PayPal and Stripe
    currency: USD receive with: Receive locally, Receive internationally (Swift), Set up Direct Debits, Receive from PayPal and Stripe
    currency: AUD receive with: Receive locally, Set up Direct Debits
    currency: NZD receive with: Receive locally
    currency: CAD receive with: Receive locally, Set up Direct Debits
    currency: HUF receive with: Receive locally
    currency: MYR receive with:
    currency: RON receive with: Receive locally
    currency: SGD receive with: Receive locally
    currency: TRY receive with: Receive locally

Balance

This code retrieves the balance for each currency in each account.

>>> profiles = client.profiles.list()
>>> for profile in profiles:
...     print(f"type: {profile.type} {', '.join(f'{balance.totalWorth.value}{balance.totalWorth.currency}' for balance in client.balances.list(profile=profile))}")
... 
type: personal 1000000.0GBP, 1000000.0EUR, 1000000.0USD, 1000000.0AUD
type: business 1000000.0GBP, 1000000.0EUR, 1000000.0USD, 1000000.0AUD

Currencies

Wise supports many currencies.

>>> currencies = client.currencies.list()
>>> AED = currencies[0]
>>> AED.code
'AED'
>>> AED.name
'United Arab Emirates dirham'
>>> AED.symbol
'د.إ'

Above are the up-to-date currencies. You can also use those in the package.

>>> from wise_banking_api_client import Currency
>>> Currency.AED.code
'AED'
>>> Currency.AED.name
'United Arab Emirates dirham'
>>> Currency.AED.symbol
'د.إ'

Recipient Account Requirements

In this example, we get the requirements for a recipient account that should receive 100 GBP from us.

>>> requirements = client.recipient_accounts.get_requirements_for_currency(source=Currency.GBP, target=Currency.GBP, source_amount=100)
>>> list(sorted([requirement.type for requirement in requirements]))
['email', 'iban', 'sort_code']

Genrally, for thie currency, we can send money to a recipient specified by email, IBAN or sort code.

Webhook signature verification

from flask import abort, request
from wise_banking_api_client.webhooks import validate_request

@app.route("/payments/wise/webhooks")
def handle_wise_webhook():
    try:
        validate_request(request)
    except Exception as e:
        logger.error(f"Wise webhook request validation failed: {e}")
        abort(400)

    ...

Request an Example Quote

You can request quote examples as stated in Create an un-authenticated quote.

In this example, we want to transfer GBP to USD and make sure we have 110USD in the end. The example quote requires less information than a real quote.

>>> from wise_banking_api_client import ExampleQuoteRequest
>>> quote_request = ExampleQuoteRequest(
...     sourceCurrency="GBP",
...     targetCurrency="USD",
...     sourceAmount=None,
...     targetAmount=110,
... )
>>> example_quote = client.quotes.example(quote_request)
>>> example_quote.rate
1.25155
>>> example_quote.rateExpirationTime
datetime.datetime(2024, 12, 31, 19, 21, 44, tzinfo=datetime.timezone.utc)
>>> example_quote.profile == None  # Example quotes are not bound to a profile
True
>>> example_quote.rateType
'FIXED'

Request a Quote

To create a transfer, you first need a quote. You can read on how to create Create an authenticated quote.

In this example, we create a quote for the personal account. This is the same quote as the one above. We also provide pay-out and pay-in information. The targetAccount is None because we don't know the recipient yet.

>>> from wise_banking_api_client import QuoteRequest, PaymentMethod
>>> quote_request = QuoteRequest(
...        sourceCurrency="EUR",
...        targetCurrency="EUR",
...        sourceAmount=None,
...        targetAmount=1,
...        targetAccount=None,
...        payOut=PaymentMethod.BANK_TRANSFER,
...        preferredPayIn=PaymentMethod.BANK_TRANSFER,
...    )
>>> quote = client.quotes.create(quote_request, profile=profiles.personal[0])
>>> quote.user
12970746
>>> quote.status
'PENDING'
>>> quote.sourceCurrency
'GBP'
>>> quote.targetCurrency 
'USD'
>>> quote.sourceAmount is None  # the source amount depends on the payment option
True
>>> quote.targetAmount
110.0
>>> quote.rate
1.24232
>>> quote.rateType
'FIXED'
>>> quote.payOut
'BANK_TRANSFER'
>>> len(quote.paymentOptions)  # we have many payment options
20

Get Recipient Requirements

The quote above lacks the recipient information. The reason is that there are requirements to the recipient account that depend on the quote. We can get these requirements using get_requirements_for_quote.

>>> requirements = client.recipient_accounts.get_requirements_for_quote(quote)
>>> [requirement.type for requirement in requirements]
['aba', 'fedwire_local', 'swift_code', 'email']

In the example above, we see different requirements for transferring money to a bank account in the USA. We can use aba, fedwire_local, swift_code and email.

If we look at the first requirement, we see that we require 10 fields.

>>> ach = requirements[0]
>>> ach.title
'ACH'
>>> len(requirements[0].fields)
10
>>> ach.fields[0].name
'Recipient type'
>>> ach.fields[0].group[0].required
True
>>> ach.fields[0].group[0].type  # the fields are grouped and this is a select with values
'select'
>>> [v.key for v in ach.fields[0].group[0].valuesAllowed]  # the JSON value for the details
['PRIVATE', 'BUSINESS']
>>> [v.name for v in ach.fields[0].group[0].valuesAllowed]  # what to show to the user
['Person', 'Business']

Create an Email Recipient

Wise: Please contact us before attempting to use email recipients. We do not recommend using this feature except for certain uses cases.

Because email is in the requirements, we can create an email recipient for the quote.

>>> email = requirements[-1]
>>> len(email.required_fields)
2
>>> email.required_fields[0].group[0].key
'email'
>>> email.required_fields[0].group[0].name
'Email (Optional)'
>>> email.required_fields[1].group[0].key
'accountHolderName'
>>> email.required_fields[1].group[0].name
'Full name of the account holder'

Below, we create the recipient and get a response. The response includes all the data that we sent and optional fields.

>>> from wise_banking_api_client import Recipient, RecipientDetails, CurrencyCode, AccountRequirementType
>>> email_recipient = Recipient(
...     currency=CurrencyCode.EUR,
...     type=AccountRequirementType.email,
...     profile=profiles.personal[0].id,
...     accountHolderName="John Doe",
...     ownedByCustomer=False,
...     details=RecipientDetails(email="john@doe.com")
... )
>>> created_response = client.recipient_accounts.create_recipient(email_recipient)
>>> created_response.id  # the response has an id
700614969

In the following, we get the actual recipient account as the user sees it e.g. in the app.

>>> recipient = client.recipient_accounts.get(created_response)
>>> recipient.id
700614969
>>> recipient.accountSummary
'john@doe.com'
>>> recipient.currency
'EUR'
>>> recipient.email
'john@doe.com'
>>> recipient.legalEntityType
'PERSON'
>>> recipient.ownedByCustomer
False

Create an IBAN recipient

Email is discouraged. We can do better! Belows we go through the flow of creating an IBAN recipient and updating the quote.

We use the business profile for this.

>>> business_profile = profiles.business[0]
  1. Create an EUR/IBAN quote. We transfer 1000 GBP to EUR.

    >>> quote_request = QuoteRequest(sourceCurrency="GBP",targetCurrency="EUR", sourceAmount=1000)
    >>> quote = client.quotes.create(quote_request, business_profile)
    >>> quote.id
    'f8301dde-cdb4-46c0-b944-3a07c7807d47'
    
  2. Get the recipient requirements for the quote.

    >>> requirements = client.recipient_accounts.get_requirements_for_quote(quote)
    >>> requirements.iban.required_keys
    ['IBAN', 'accountHolderName', 'legalType']
    
  3. Create an IBAN recipient.

    >>> from wise_banking_api_client import Recipient, RecipientDetails, CurrencyCode, AccountRequirementType, LegalType
    >>> iban_recipient = Recipient(
    ...     currency=CurrencyCode.EUR,
    ...     type=AccountRequirementType.iban,
    ...     profile=business_profile.id,
    ...     accountHolderName="Max Mustermann", # required, see above
    ...     ownedByCustomer=False,
    ...     details=RecipientDetails(
    ...         legalType=LegalType.PRIVATE,    # required, see above
    ...         IBAN="DE75512108001245126199"   # required, see above
    ...     )
    ... )
    >>> created_iban_recipient = client.recipient_accounts.create_recipient(iban_recipient)
    >>> created_iban_recipient.id
    700615308
    
  4. Update the quote so that it works with the IBAN recipient.

    >>> quote = client.quotes.update(created_iban_recipient, quote)
    >>> quote.status
    'PENDING'
    
  5. Check the requirements again.

    The fields tell you if you need to check the requirements again. For example, the recipient type changes the required fields.

    >>> requirements = client.recipient_accounts.get_requirements_for_quote(quote)
    >>> requirements.iban.required_keys
    ['IBAN', 'accountHolderName', 'legalType']
    

Above, we saw the flow of creating an IBAN recipient tied to the quote.

Check the Prices

A Quote includes different pricing options. These depend on the payIn and payOut options of the quote or the transfer.

Below, we show all the available options to pay for the transfer, sorted by price.

>>> for po in sorted(quote.enabled_payments, key=lambda po: po.fee.total):
...     print(f"fee: {po.fee.total: <6} payIn: {po.payIn: <21} payOut: {po.payOut}")
fee: 3.69   payIn: BALANCE               payOut: BANK_TRANSFER
fee: 3.88   payIn: BANK_TRANSFER         payOut: BANK_TRANSFER
fee: 3.88   payIn: PISP                  payOut: BANK_TRANSFER
fee: 3.88   payIn: SWIFT                 payOut: BANK_TRANSFER
fee: 7.56   payIn: VISA_DEBIT_OR_PREPAID payOut: BANK_TRANSFER
fee: 10.42  payIn: DEBIT                 payOut: BANK_TRANSFER
fee: 10.42  payIn: MC_DEBIT_OR_PREPAID   payOut: BANK_TRANSFER
fee: 10.42  payIn: CARD                  payOut: BANK_TRANSFER
fee: 10.42  payIn: MAESTRO               payOut: BANK_TRANSFER
fee: 16.28  payIn: MC_CREDIT             payOut: BANK_TRANSFER
fee: 19.09  payIn: VISA_BUSINESS_DEBIT   payOut: BANK_TRANSFER
fee: 26.09  payIn: CREDIT                payOut: BANK_TRANSFER
fee: 26.09  payIn: APPLE_PAY             payOut: BANK_TRANSFER
fee: 26.09  payIn: VISA_CREDIT           payOut: BANK_TRANSFER
fee: 26.09  payIn: GOOGLE_PAY            payOut: BANK_TRANSFER
fee: 35.43  payIn: MC_BUSINESS_DEBIT     payOut: BANK_TRANSFER
fee: 43.14  payIn: MC_BUSINESS_CREDIT    payOut: BANK_TRANSFER
fee: 43.14  payIn: VISA_BUSINESS_CREDIT  payOut: BANK_TRANSFER
fee: 55.66  payIn: INTERNATIONAL_DEBIT   payOut: BANK_TRANSFER
fee: 55.66  payIn: INTERNATIONAL_CREDIT  payOut: BANK_TRANSFER

We can see that to pay with the balance by bank transfer is the cheapest option.

Create a Transfer

We can now create a transfer.

  1. Create a transfer request with the minimal required data of the transfer.

    >>> from wise_banking_api_client import TransferRequest, TransferDetails
    >>> transfer_request = TransferRequest(
    ...     targetAccount=created_iban_recipient.id,
    ...     quoteUuid=quote.id,
    ...     details=TransferDetails(
    ...         reference="Geschenk"
    ...     )
    ... )
    >>> transfer = client.transfers.create(transfer_request)
    >>> transfer.status
    'incoming_payment_waiting'
    

    At this point, we can see the transfer in the user interface:

  2. Fund the transfer.

    Funding transfers requires SCA authentication. You can read on how to configure SCA here. You can use the default key located at wise_banking_api_client.DEFAULT_PRIVATE_KEY for the sandbox API and upload it to your account's business profile.

    >>> from wise_banking_api_client import DEFAULT_PRIVATE_KEY
    >>> client = Client(api_key="...", private_key_file=DEFAULT_PRIVATE_KEY)
    >>> funding_result = client.transfers.fund(transfer)
    >>> funding_result.status
    'COMPLETED'
    
  3. Sandbox Transfer Simulation

    The sandbox API allows you to simulate transfers.

    >>> transfer.status
    'incoming_payment_waiting'
    >>> client.simulate_transfer.to_funds_converted(transfer)  # simulate the transfer change
    >>> transfer = client.transfers.get(transfer)  # update the transfer
    >>> transfer.status  # check the status again
    'processing'
    

Development

To setup this project for development, you need

Clone the repository:

git clone https://github.com/foss-fund/wise-banking-api-client.git
cd wise-banking-api-client

Run tests

# Within the wise_banking_api_client working directory
pip install tox
tox

You can also run the tests against the sandbox API:

WISE_API_KEY="12345678-1234-1234-1234-123456789abcde" tox -e py312

Format Code

We use black to format the code.

tox -e black

Build Binaries

You can build the package and validate its correctness:

tox -e build

New Release

To create a new release:

  1. Edit the Changelog section to add all the changes.

  2. Create a new commit and wait for the build tests to finish:

    git add .
    git commit -m "v0.0.2"
    git push
    
  3. Create a tag for the commit:

    git tag v0.0.2
    git push origin v0.0.2
    

The CI should build, test and push a new version to PyPI.

Changelog

v0.0.3

  • Test compatibility with Python 3.13

v0.0.2

  • Remove unused test environments
  • Link to pywisetransfer and explain difference
  • Add Release Section

v0.0.1

  • Initial release

License

The code is released under the AGPL License.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

wise_banking_api_client-0.0.3.tar.gz (512.1 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

wise_banking_api_client-0.0.3-py3-none-any.whl (355.3 kB view details)

Uploaded Python 3

File details

Details for the file wise_banking_api_client-0.0.3.tar.gz.

File metadata

  • Download URL: wise_banking_api_client-0.0.3.tar.gz
  • Upload date:
  • Size: 512.1 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.1.0 CPython/3.12.8

File hashes

Hashes for wise_banking_api_client-0.0.3.tar.gz
Algorithm Hash digest
SHA256 3b6da1126b097288ed02a6aabf33505f6c39953090ca17d40cc41d9e0c774649
MD5 e8f2b4a972eea5feaa28389ffa6bbae0
BLAKE2b-256 407d64b32af1b74550b23e81ea651e74332d751303b01b0ef63308f262124442

See more details on using hashes here.

File details

Details for the file wise_banking_api_client-0.0.3-py3-none-any.whl.

File metadata

File hashes

Hashes for wise_banking_api_client-0.0.3-py3-none-any.whl
Algorithm Hash digest
SHA256 5126946fd7d1007ff0da60568d8d242dd276932239f6581d2bc3af58f7f5e59c
MD5 fa2f3a7e31809b0a0d62845e7b63f2a6
BLAKE2b-256 fa9ac4278ccfea4ca92f6d51c052aa54d36c057726c1eceee21a88996b0b443b

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page