Skip to content

Reference

shared.services.payment_providers.revolut.entities

CounterPartyInfo dataclass

CounterPartyInfo(counterparty_id, account_id)

Bases: DataClassJsonMixin

account_id instance-attribute

account_id

counterparty_id instance-attribute

counterparty_id

RevolutBusinessAccountConfig dataclass

RevolutBusinessAccountConfig(
    base_url,
    api_credentials_secret_name,
    private_key,
    private_key_secret_name,
    client_id,
    issuer,
    reimbursement_account_id=None,
    webhook_signing_secret=None,
    webhook_signing_secret_name=None,
)

api_credentials_secret_name instance-attribute

api_credentials_secret_name

base_url instance-attribute

base_url

client_id instance-attribute

client_id

issuer instance-attribute

issuer

private_key instance-attribute

private_key

private_key_secret_name instance-attribute

private_key_secret_name

reimbursement_account_id class-attribute instance-attribute

reimbursement_account_id = None

webhook_signing_secret class-attribute instance-attribute

webhook_signing_secret = None

webhook_signing_secret_name class-attribute instance-attribute

webhook_signing_secret_name = None

RevolutBusinessAccountName

Bases: AlanBaseEnum

alan_insurance class-attribute instance-attribute

alan_insurance = 'alan_insurance'

alan_sa class-attribute instance-attribute

alan_sa = 'alan_sa'

alan_services class-attribute instance-attribute

alan_services = 'alan_services'

marmot_be_belfius_bank class-attribute instance-attribute

marmot_be_belfius_bank = 'marmot_be_belfius_bank'

marmot_be_belfius_insurance class-attribute instance-attribute

marmot_be_belfius_insurance = 'marmot_be_belfius_insurance'

RevolutBusinessAccountNamesAvailableByCountry

belgium class-attribute instance-attribute

belgium = (alan_insurance,)

france class-attribute instance-attribute

france = (alan_insurance, alan_services)

prevoyance_france class-attribute instance-attribute

prevoyance_france = (alan_insurance,)

spain class-attribute instance-attribute

spain = (alan_insurance,)

RevolutPrevoyancePaymentAccount dataclass

RevolutPrevoyancePaymentAccount(lamie, cnp, alan=None)

This dataclass is storing the name of the env vars used to setup Revolut client. We introduced the model when we had to switch our Revolut business accounts from Alan SA to Alan Insurance.

Alan property is nullable as the bank account for Alan insurer was created immediately on Alan Insurance business account.

alan class-attribute instance-attribute

alan = None

cnp instance-attribute

cnp

lamie instance-attribute

lamie

RevolutTransactionInfo dataclass

RevolutTransactionInfo(
    id,
    type,
    request_id,
    state,
    created_at,
    updated_at,
    completed_at=None,
    reason_code=None,
    related_transaction_id=None,
    reference=None,
    legs=list(),
)

https://developer.revolut.com/docs/business/get-transaction#response ⧉

Leg dataclass

Leg(
    leg_id,
    amount,
    currency,
    account_id,
    fee=None,
    bill_amount=None,
    bill_currency=None,
    description=None,
    balance=None,
)
account_id instance-attribute
account_id
amount instance-attribute
amount

Can be positive or negative according to the direction of the transaction

balance class-attribute instance-attribute
balance = None
bill_amount class-attribute instance-attribute
bill_amount = None

Can be positive or negative according to the direction of the transaction

bill_currency class-attribute instance-attribute
bill_currency = None
currency instance-attribute
currency
description class-attribute instance-attribute
description = None
fee class-attribute instance-attribute
fee = None
leg_id instance-attribute
leg_id

completed_at class-attribute instance-attribute

completed_at = None

created_at instance-attribute

created_at

id instance-attribute

id

legs class-attribute instance-attribute

legs = field(default_factory=list)

reason_code class-attribute instance-attribute

reason_code = None

The reason code when the transaction state is declined or failed.

reference class-attribute instance-attribute

reference = None

related_transaction_id class-attribute instance-attribute

related_transaction_id = None

request_id instance-attribute

request_id

state instance-attribute

state

type instance-attribute

type

updated_at instance-attribute

updated_at

RevolutTransactionState

Bases: AlanBaseEnum

Possible states for a payment transaction.

completed class-attribute instance-attribute

completed = 'completed'

created class-attribute instance-attribute

created = 'created'

declined class-attribute instance-attribute

declined = 'declined'

failed class-attribute instance-attribute

failed = 'failed'

pending class-attribute instance-attribute

pending = 'pending'

reverted class-attribute instance-attribute

reverted = 'reverted'

RevolutTransactionType

Bases: AlanBaseEnum

Possible types for a payment transaction.

atm class-attribute instance-attribute

atm = 'atm'

card_chargeback class-attribute instance-attribute

card_chargeback = 'card_chargeback'

card_credit class-attribute instance-attribute

card_credit = 'card_credit'

card_payment class-attribute instance-attribute

card_payment = 'card_payment'

card_refund class-attribute instance-attribute

card_refund = 'card_refund'

exchange class-attribute instance-attribute

exchange = 'exchange'

fee class-attribute instance-attribute

fee = 'fee'

interest class-attribute instance-attribute

interest = 'interest'

loan class-attribute instance-attribute

loan = 'loan'

refund class-attribute instance-attribute

refund = 'refund'

tax class-attribute instance-attribute

tax = 'tax'

tax_refund class-attribute instance-attribute

tax_refund = 'tax_refund'

topup class-attribute instance-attribute

topup = 'topup'

topup_return class-attribute instance-attribute

topup_return = 'topup_return'

transfer class-attribute instance-attribute

transfer = 'transfer'

TransactionCreatedEvent dataclass

TransactionCreatedEvent(
    transaction_id,
    account_id,
    state,
    created_at,
    updated_at,
    business_account_name,
    raw_event,
    type,
    request_id,
    related_transaction_id=None,
    legs=list(),
)

Event for Revolut TransactionCreated webhooks.

Emitted when a new transaction is created in Revolut. https://developer.revolut.com/docs/guides/manage-accounts/webhooks/about-webhooks#transaction-created-event ⧉

account_id instance-attribute

account_id

business_account_name instance-attribute

business_account_name

created_at instance-attribute

created_at

legs class-attribute instance-attribute

legs = field(default_factory=list)

raw_event instance-attribute

raw_event

related_transaction_id class-attribute instance-attribute

related_transaction_id = None

Only for refund transactions

request_id instance-attribute

request_id

Request ID used to extract payment_id

state instance-attribute

state

transaction_id instance-attribute

transaction_id

type instance-attribute

type

updated_at instance-attribute

updated_at

TransactionStateChangedEvent dataclass

TransactionStateChangedEvent(
    transaction_id,
    payment_id,
    state,
    old_state,
    updated_at,
    business_account_name,
    raw_event,
    is_refund=False,
)

Event for Revolut TransactionStateChanged webhooks.

Emitted when an existing transaction changes state. https://developer.revolut.com/docs/guides/manage-accounts/webhooks/about-webhooks#transaction-state-changed-event ⧉

business_account_name instance-attribute

business_account_name

is_refund class-attribute instance-attribute

is_refund = False

old_state instance-attribute

old_state

payment_id instance-attribute

payment_id

raw_event instance-attribute

raw_event

state instance-attribute

state

transaction_id instance-attribute

transaction_id

updated_at instance-attribute

updated_at

shared.services.payment_providers.revolut.errors

PotentialSwiftError

PotentialSwiftError(code=None, message=None)

Bases: RevolutError

A special case of RevolutError. Raised when we think it may be fixable by resetting the SWIFT. Context: many calls to /counterparty fail because we supply the wrong SWIFT number. We don't get details from the 400 response from Revolut, but we want to communicate that it's possibly caused by a SWIFT problem.

Source code in shared/services/payment_providers/revolut/errors.py
4
5
6
def __init__(self, code=None, message=None) -> None:  # type: ignore[no-untyped-def]
    self.code = code
    self.message = message

RevolutError

RevolutError(code=None, message=None)

Bases: Exception

A generic revolut exception we should handle

Source code in shared/services/payment_providers/revolut/errors.py
4
5
6
def __init__(self, code=None, message=None) -> None:  # type: ignore[no-untyped-def]
    self.code = code
    self.message = message

code instance-attribute

code = code

message instance-attribute

message = message

RevolutInternalError

RevolutInternalError(code=None, message=None)

Bases: RevolutError

Revolut issue, we can't act on it

Source code in shared/services/payment_providers/revolut/errors.py
4
5
6
def __init__(self, code=None, message=None) -> None:  # type: ignore[no-untyped-def]
    self.code = code
    self.message = message

RevolutNotAuthorizedError

RevolutNotAuthorizedError(code=None, message=None)

Bases: RevolutError

The access token is not valid.

We have a scheduled job running every 15 minutes to rotate the access token as it is valid for less than 1h. If the command failed to run for some reason, the token won't have been rotated, and will thus be expired / invalid.

See also https://www.notion.so/alaninsurance/Revolut-API-keys-Management-d536aabbf7ba4c278dc86a2da05b6b21#1cca1b9404314144bdc58f2fbd06b791 ⧉

Source code in shared/services/payment_providers/revolut/errors.py
4
5
6
def __init__(self, code=None, message=None) -> None:  # type: ignore[no-untyped-def]
    self.code = code
    self.message = message

RevolutNotFoundError

RevolutNotFoundError(code=None, message=None)

Bases: RevolutError

Resource does not exist on Revolut, it is up to us to decide whether we take this error into account or not

Source code in shared/services/payment_providers/revolut/errors.py
4
5
6
def __init__(self, code=None, message=None) -> None:  # type: ignore[no-untyped-def]
    self.code = code
    self.message = message

RevolutParallelRequestsError

RevolutParallelRequestsError(code=None, message=None)

Bases: RevolutError

See https://www.notion.so/alaninsurance/Revolut-payments-and-its-failures-68efe555a6a14610835b928ce09de883#ee51064d48de444f9763fbf66831b34d ⧉

Source code in shared/services/payment_providers/revolut/errors.py
4
5
6
def __init__(self, code=None, message=None) -> None:  # type: ignore[no-untyped-def]
    self.code = code
    self.message = message

shared.services.payment_providers.revolut.helpers

EEA_COUNTRIES module-attribute

EEA_COUNTRIES = [
    "AT",
    "AX",
    "BE",
    "BG",
    "CY",
    "CZ",
    "DE",
    "DK",
    "EA",
    "EE",
    "ES",
    "FI",
    "FR",
    "GB",
    "GF",
    "GI",
    "GP",
    "GR",
    "HR",
    "HU",
    "IC",
    "IE",
    "IT",
    "LT",
    "LU",
    "LV",
    "MF",
    "MQ",
    "MT",
    "NL",
    "PL",
    "PT",
    "RE",
    "RO",
    "SE",
    "SI",
    "SK",
    "YT",
    "NO",
    "IS",
    "LI",
    "JE",
    "GG",
    "IM",
]

PREVOYANCE_BANK_ACCOUNT_CONFIG_MAPPING module-attribute

PREVOYANCE_BANK_ACCOUNT_CONFIG_MAPPING = {
    alan_insurance: RevolutPrevoyancePaymentAccount(
        cnp="REVOLUT_ALAN_INSURANCE_PREVOYANCE_CNP_PAYMENT_ACCOUNT_ID",
        lamie="REVOLUT_ALAN_INSURANCE_PREVOYANCE_LAMIE_PAYMENT_ACCOUNT_ID",
        alan="REVOLUT_ALAN_INSURANCE_PREVOYANCE_ALAN_PAYMENT_ACCOUNT_ID",
    )
}

get_revolut_config_from_business_account_name

get_revolut_config_from_business_account_name(account_name)
Source code in shared/services/payment_providers/revolut/helpers.py
def get_revolut_config_from_business_account_name(
    account_name: RevolutBusinessAccountName,
) -> RevolutBusinessAccountConfig:
    if RevolutBusinessAccountName[account_name] not in RevolutBusinessAccountName:
        raise ValueError(
            f"Invalid Revolut business account name {account_name}. "
            f"Valid values are: {', '.join(RevolutBusinessAccountName.get_values())}"
        )

    revolut_config = _business_account_name_mapping[account_name]

    if not revolut_config:
        raise ValueError(
            f"Could not find Revolut config for business account name {account_name}"
        )

    return revolut_config

is_reimbursement_transaction

is_reimbursement_transaction(transaction)

Check if a Revolut transaction is linked to Alan's Revolut reimbursement account

Source code in shared/services/payment_providers/revolut/helpers.py
def is_reimbursement_transaction(transaction: RevolutTransactionInfo) -> bool:
    """
    Check if a Revolut transaction is linked to Alan's Revolut reimbursement account
    """

    reimbursement_account_ids = {
        # Revolut account for Health insurance
        current_config.get("REVOLUT_ALAN_INSURANCE_REIMBURSEMENT_ACCOUNT_ID"),
        # Prevoyance bank accounts declared on Alan Insurance business account.
        # CNP & Lamie are not used yet (see previous comment), Alan will be used starting 2024-01-01
        current_config.get("REVOLUT_ALAN_INSURANCE_PREVOYANCE_CNP_PAYMENT_ACCOUNT_ID"),
        current_config.get(
            "REVOLUT_ALAN_INSURANCE_PREVOYANCE_LAMIE_PAYMENT_ACCOUNT_ID"
        ),
        current_config.get("REVOLUT_ALAN_INSURANCE_PREVOYANCE_ALAN_PAYMENT_ACCOUNT_ID"),
    }

    account_ids = [leg.account_id for leg in transaction.legs]

    return any(
        reimbursement_account_id in account_ids
        for reimbursement_account_id in reimbursement_account_ids
    )

shared.services.payment_providers.revolut.noop_revolut_client

NoopRevolutClient

NoopRevolutClient(*, transaction_info=None)

Revolut client that does nothing.

Can be configured with test data for get_transaction.

Initialize NoopRevolutClient.

Parameters:

Name Type Description Default
transaction_info RevolutTransactionInfo | None

Optional transaction info to return from get_transaction. If not provided, returns default failed transaction.

None
Source code in shared/services/payment_providers/revolut/noop_revolut_client.py
def __init__(
    self,
    *,
    transaction_info: RevolutTransactionInfo | None = None,
) -> None:
    """
    Initialize NoopRevolutClient.

    Args:
        transaction_info: Optional transaction info to return from get_transaction.
            If not provided, returns default failed transaction.
    """
    self._transaction_info = transaction_info

add_counterparty

add_counterparty(
    *,
    company_name=None,
    first_name=None,
    last_name=None,
    iban,
    bic,
    bank_country=None,
    address
)
Source code in shared/services/payment_providers/revolut/noop_revolut_client.py
def add_counterparty(
    self,
    *,
    # This field must be specified if the recipient is a company or a hospital
    company_name: str | None = None,  # noqa: ARG002
    # These two fields must be specified if the recipient is an individual
    first_name: str | None = None,  # noqa: ARG002
    last_name: str | None = None,  # noqa: ARG002
    iban: str,  # noqa: ARG002
    bic: str | None,  # noqa: ARG002
    bank_country: str | None = None,  # noqa: ARG002
    address: BillingAddress | None,  # noqa: ARG002
) -> CounterPartyInfo:
    # We generate fake `counterparty_id` and `account_id`
    return CounterPartyInfo(str(uuid.uuid4()), str(uuid.uuid4()))

create_payment

create_payment(
    *,
    request_id,
    amount_in_cents,
    receiver_counterparty_id,
    receiver_account_id,
    payment_description=None
)
Source code in shared/services/payment_providers/revolut/noop_revolut_client.py
def create_payment(
    self,
    *,
    # The request_id is controlled by us, and is a string of 40 chars max
    request_id: str,
    amount_in_cents: int,  # noqa: ARG002
    receiver_counterparty_id: str,
    receiver_account_id: str,
    payment_description: str | None = None,  # noqa: ARG002
) -> PaymentInfo:
    # We generate a fake `payment_id`
    return PaymentInfo(
        counterparty_id=receiver_counterparty_id,
        account_id=receiver_account_id,
        transaction_id=str(uuid.uuid4()),
        request_id=request_id,
        created_at=datetime.now(),
        transfer_type=PaymentTransferType.instant,
        currency="EUR",
    )

get_supported_currency

get_supported_currency()
Source code in shared/services/payment_providers/revolut/noop_revolut_client.py
def get_supported_currency(self) -> str:
    return "EUR"

get_transaction

get_transaction(
    transaction_id=None,
    request_id=None,
    ignore_not_found_error=False,
)
Source code in shared/services/payment_providers/revolut/noop_revolut_client.py
def get_transaction(
    self,
    transaction_id: str | None = None,
    request_id: str | None = None,
    ignore_not_found_error: bool = False,  # noqa: ARG002
) -> RevolutTransactionInfo:
    # Return configured transaction info if provided
    if self._transaction_info:
        return self._transaction_info

    # Otherwise return default failed transaction
    return RevolutTransactionInfo(
        id=transaction_id or str(uuid.uuid4()),
        request_id=request_id or str(uuid.uuid4()),
        state=RevolutTransactionState.failed,
        created_at=datetime.fromisoformat("2023-03-12T12:08:07.833414"),
        updated_at=datetime.fromisoformat("2023-03-13T12:08:07.833705"),
        completed_at=datetime.fromisoformat("2023-03-13T12:08:07.833705"),
        reference=None,
        type=RevolutTransactionType.transfer,
        legs=[],
    )

shared.services.payment_providers.revolut.openapi

business

Account

Bases: BaseModel

balance instance-attribute
balance

The current balance on the account.

created_at instance-attribute
created_at

The date and time the account was created in ISO 8601 ⧉ format.

currency instance-attribute
currency
id instance-attribute
id

The account ID.

model_config class-attribute instance-attribute
model_config = ConfigDict(extra='allow')
name class-attribute instance-attribute
name = None

The account name.

public instance-attribute
public

Indicates whether the account is visible to other businesses on Revolut.

state instance-attribute
state

Indicates the state of the account.

updated_at instance-attribute
updated_at

The date and time the account was last updated in ISO 8601 ⧉ format.

AccountBankDetailsItem

Bases: BaseModel

account_no class-attribute instance-attribute
account_no = None

The account number.

bank_country class-attribute instance-attribute
bank_country = None
beneficiary instance-attribute
beneficiary

The name of the counterparty.

beneficiary_address instance-attribute
beneficiary_address
bic class-attribute instance-attribute
bic = None

The BIC number, also known as SWIFT code.

estimated_time instance-attribute
estimated_time
iban class-attribute instance-attribute
iban = None

The IBAN number.

model_config class-attribute instance-attribute
model_config = ConfigDict(extra='allow')
pooled class-attribute instance-attribute
pooled = None

Indicates whether the account address is pooled or unique.

routing_number class-attribute instance-attribute
routing_number = None

The routing number of the account.

schemes instance-attribute
schemes

The schemes that are available for this currency account.

sort_code class-attribute instance-attribute
sort_code = None

The sort code of the account.

unique_reference class-attribute instance-attribute
unique_reference = None

The reference of the pooled account.

AccountBankDetailsItems

Bases: RootModel[list[AccountBankDetailsItem]]

root instance-attribute
root

Indicates the payment scheme used to execute transactions.

AccountCurrency

Bases: RootModel[str]

root instance-attribute
root

ISO 4217 ⧉ currency code in upper case.

AccountNameValidationReasonAU

Bases: BaseModel

code class-attribute instance-attribute
code = None

The reason code. Possible values for AU: - close_match (business accounts): The provided name is similar to the account name. The actual name is returned. Mismatched account type is corrected. - not_matched: The account details don't match the provided values. - account_does_not_exist: The account does not exist. - account_switched: The account has been switched using the Current Account Switching Service. Please contact the recipient for updated account details. - cannot_be_checked: The account cannot be checked.

model_config class-attribute instance-attribute
model_config = ConfigDict(extra='allow')
type class-attribute instance-attribute
type = None

The reason type. Determines the service ⧉ used for the validation that returned the reason code. For AU, the value is: au_cop.

AccountNameValidationReasonEUR

Bases: BaseModel

code class-attribute instance-attribute
code = None

The reason code. Possible values for EUR: - close_match: The provided name is similar to the account name. The actual name is returned. - not_matched: The account details don't match the provided values. - account_does_not_exist: The account does not exist. - account_switched: The account has been switched using the Current Account Switching Service. Please contact the recipient for updated account details. - cannot_be_checked: The account cannot be checked.

model_config class-attribute instance-attribute
model_config = ConfigDict(extra='allow')
type class-attribute instance-attribute
type = None

The reason type. Determines the service ⧉ used for the validation that returned the reason code. For EUR, the value is: eu_cop.

AccountNameValidationReasonRO

Bases: BaseModel

code class-attribute instance-attribute
code = None

The reason code. Possible values for RO: - cannot_be_checked: The account cannot be checked.

model_config class-attribute instance-attribute
model_config = ConfigDict(extra='allow')
type class-attribute instance-attribute
type = None

The reason type. Determines the service ⧉ used for the validation that returned the reason code. For RO, the value is: ro_cop.

AccountNameValidationReasonUK

Bases: BaseModel

code class-attribute instance-attribute
code = None

The reason code. Possible values for UK: - close_match: The provided name is similar to the account name, the account type is correct. The actual name is returned. - individual_account_name_matched: The names match but the counterparty is an individual, not a business. - company_account_name_matched: The names match but the counterparty is a business, not an individual. - individual_account_close_match: The provided name is similar to the account name, and the account type is incorrect – the counterparty is an individual, not a business. The actual name is returned. - company_account_close_match: The provided name is similar to the account name, and the account type is incorrect - the counterparty is a business, not an individual. The actual name is returned. - not_matched: The account details don't match the provided values. - account_does_not_exist: The account does not exist. - account_switched: The account has been switched using the Current Account Switching Service. Please contact the recipient for updated account details. - cannot_be_checked: The account cannot be checked.

model_config class-attribute instance-attribute
model_config = ConfigDict(extra='allow')
type class-attribute instance-attribute
type = None

The reason type. Determines the service ⧉ used for the validation that returned the reason code. For UK, the values is: uk_cop.

Accounts

Bases: RootModel[list[Account]]

root instance-attribute
root

Amount

Bases: RootModel[float]

root instance-attribute
root

The amount of money.

Amount1

Bases: BaseModel

amount class-attribute instance-attribute
amount = None
currency class-attribute instance-attribute
currency = None
model_config class-attribute instance-attribute
model_config = ConfigDict(extra='allow')

AmountWithCurrency

Bases: BaseModel

amount class-attribute instance-attribute
amount = None

The amount of the transaction.

currency class-attribute instance-attribute
currency = None
model_config class-attribute instance-attribute
model_config = ConfigDict(extra='allow')

BankCountryCode

Bases: RootModel[str]

root instance-attribute
root

The country of the bank as 2-letter ISO 3166 ⧉ code.

BeneficiaryAddress

Bases: BaseModel

city class-attribute instance-attribute
city = None

The name of the city.

country instance-attribute
country

The country of the counterparty as 2-letter ISO 3166 ⧉ code.

model_config class-attribute instance-attribute
model_config = ConfigDict(extra='allow')
postcode instance-attribute
postcode

The postcode of the counterparty address.

region class-attribute instance-attribute
region = None

The name of the region.

street_line1 class-attribute instance-attribute
street_line1 = None

Street line 1 information.

street_line2 class-attribute instance-attribute
street_line2 = None

Street line 2 information.

BusinessMerchantCategory

Bases: RootModel[Literal['health', 'general', 'services', 'airlines', 'transport', 'accommodation', 'utilities', 'shopping', 'financial', 'furniture', 'hardware', 'groceries', 'fuel', 'entertainment', 'software', 'restaurants', 'advertising', 'cash', 'education', 'government']]

root instance-attribute
root

CardCanBeUnlocked

Bases: RootModel[bool]

root instance-attribute
root

Returned for locked cards (state=locked). Indicates whether the card can be unlocked ⧉ manually (via API or in-app). If true, you'll still need the necessary scope or permission ⧉ to unlock the card.

Info

Cards can be locked for various reasons. For example, a card can be locked by the user, due to spending period settings, or automatically by the system. Only certain types of lock can be lifted manually.

CardContact

Bases: RootModel[UUID]

root instance-attribute
root

One of the card's contacts.

CardContacts

Bases: RootModel[list[CardContact]]

root instance-attribute
root

The list of contacts for a company card ⧉.

CardCreatedResponse

Bases: BaseModel

accounts instance-attribute
accounts

The list of linked accounts.

can_be_unlocked class-attribute instance-attribute
can_be_unlocked = None
categories class-attribute instance-attribute
categories = None

The list of merchant categories that are available for card spending. If this parameter is not specified, categories are not restricted.

contact_ids class-attribute instance-attribute
contact_ids = None
countries class-attribute instance-attribute
countries = None

The list of countries where the card can be used, specified as 2-letter ISO 3166 ⧉ codes.

created_at instance-attribute
created_at

The date and time the card was created in ISO 8601 ⧉ format.

expiry instance-attribute
expiry

The card expiration date.

holder_id class-attribute instance-attribute
holder_id = None

The ID of the team member who is the holder of the card. If the card belongs to the business, this will be empty.

For more information, see the guides: Manage Cards - Create a virtual card ⧉.

id instance-attribute
id

The ID of the card.

label class-attribute instance-attribute
label = None

The label of the card.

last_digits instance-attribute
last_digits

The last 4 digits of the card's PAN.

merchant_controls class-attribute instance-attribute
merchant_controls = None

The merchant-level controls for card spending.

They block or allow the card to only transact with specific merchants: - allow: permits only the specified merchants (cannot be used if the categories parameter is set) - block: blocks the specified merchants (can be used with or without categories)

model_config class-attribute instance-attribute
model_config = ConfigDict(extra='allow')
product class-attribute instance-attribute
product = None
references class-attribute instance-attribute
references = None
spending_limits class-attribute instance-attribute
spending_limits = None
spending_period class-attribute instance-attribute
spending_period = None

The controls for the card's spending period.

They specify the dates when the card becomes available or unavailable for spending, and define what happens after the end date.

state instance-attribute
state

The state that the card is in.

updated_at instance-attribute
updated_at

The date and time the card was last updated in ISO 8601 ⧉ format.

virtual instance-attribute
virtual

Specifies whether the card is virtual (true) or physical (false).

CardInvitationAccounts

Bases: RootModel[list[UUID]]

root instance-attribute
root

The list of accounts that will be linked to the card.

CardInvitationCardId

Bases: RootModel[UUID]

root instance-attribute
root

The ID of the card issued after this invitation was claimed.

Note

Only returned for invitations in state redeemed.

CardInvitationCategories

Bases: RootModel[list[BusinessMerchantCategory]]

root instance-attribute
root

The list of merchant categories that will be available for card spending. If this parameter is not specified, categories are not restricted.

CardInvitationCountries

Bases: RootModel[list[CardInvitationCountry]]

root instance-attribute
root

The list of countries where the team member will be able to use the card. Specified as 2-letter ISO 3166 ⧉ codes.

CardInvitationCountry

Bases: RootModel[str]

root instance-attribute
root

CardInvitationCreatedAt

Bases: RootModel[AwareDatetime]

root instance-attribute
root

The date and time the card invitation was created in ISO 8601 ⧉ format.

CardInvitationCreatedHolderId

Bases: RootModel[UUID]

root instance-attribute
root

The ID of the team member to be assigned as the holder of the card after the invitation is claimed.

CardInvitationCreatedResponse

Bases: BaseModel

accounts instance-attribute
accounts
categories class-attribute instance-attribute
categories = None
countries class-attribute instance-attribute
countries = None
created_at instance-attribute
created_at
expiry_date class-attribute instance-attribute
expiry_date = None
holder_id class-attribute instance-attribute
holder_id = None
id instance-attribute
id
label class-attribute instance-attribute
label = None
merchant_controls class-attribute instance-attribute
merchant_controls = None
model_config class-attribute instance-attribute
model_config = ConfigDict(extra='allow')
spending_limits class-attribute instance-attribute
spending_limits = None
spending_period class-attribute instance-attribute
spending_period = None
state instance-attribute
state
updated_at instance-attribute
updated_at
virtual instance-attribute
virtual

CardInvitationExpiryDate

Bases: RootModel[AwareDatetime]

root instance-attribute
root

The date and time after which this card invitation expires if not claimed or cancelled before then. Specified in ISO 8601 ⧉ format.

Note

Only returned for invitations in state created.

Tip

For other states, to find out when a card invitation transitioned to its final state ⧉, check the updated_at value.

CardInvitationHolderId

Bases: RootModel[UUID]

root instance-attribute
root

The ID of the team member to be assigned as the holder of the card after the invitation is claimed.

Note

If the team member has been deleted since the invitation was created, the holder_id is not returned.

CardInvitationId

Bases: RootModel[UUID]

root instance-attribute
root

The ID of the card invitation.

CardInvitationLabel

Bases: RootModel[str]

root instance-attribute
root

The label of the card.

CardInvitationMerchantControls

Bases: BaseModel

control_type instance-attribute
control_type

The type of control to apply.

merchant_ids instance-attribute
merchant_ids

The list of IDs of merchants to which the control applies.

Tip

To find merchant IDs, check transaction details (→ merchant.id). You can fetch transaction details for a specific transaction ⧉ or for all transactions ⧉.

model_config class-attribute instance-attribute
model_config = ConfigDict(extra='allow')

CardInvitationResponse

Bases: BaseModel

accounts instance-attribute
accounts
card_id class-attribute instance-attribute
card_id = None
categories class-attribute instance-attribute
categories = None
countries class-attribute instance-attribute
countries = None
created_at instance-attribute
created_at
expiry_date class-attribute instance-attribute
expiry_date = None
holder_id class-attribute instance-attribute
holder_id = None
id instance-attribute
id
label class-attribute instance-attribute
label = None
merchant_controls class-attribute instance-attribute
merchant_controls = None
model_config class-attribute instance-attribute
model_config = ConfigDict(extra='allow')
spend_program class-attribute instance-attribute
spend_program = None
spending_limits class-attribute instance-attribute
spending_limits = None
spending_period class-attribute instance-attribute
spending_period = None
state instance-attribute
state
updated_at instance-attribute
updated_at
virtual instance-attribute
virtual

CardInvitationSpendProgram

Bases: RootModel[SpendProgram]

root instance-attribute
root

CardInvitationSpendingLimits

Bases: RootModel[SpendingLimits]

root instance-attribute
root

CardInvitationSpendingPeriod

Bases: RootModel[CardInvitationSpendingPeriod4 | CardInvitationSpendingPeriod5]

root instance-attribute
root

The controls for the card's spending period.

They specify the dates when the card will become available or unavailable for spending, and define what happens after the end date.

CardInvitationSpendingPeriod1

Bases: BaseModel

end_date class-attribute instance-attribute
end_date = None

The end date (inclusive) of the spending period, in ISO 8601 ⧉ format (YYYY-MM-DD). Uses the timezone set by the business ⧉, or defaults to Europe/London.

end_date_action class-attribute instance-attribute
end_date_action = None

The action to take after the end date of the spending period.

model_config class-attribute instance-attribute
model_config = ConfigDict(extra='allow')
start_date instance-attribute
start_date

The start date (inclusive) of the spending period, in ISO 8601 ⧉ format (YYYY-MM-DD). Uses the timezone set by the business ⧉, or defaults to Europe/London.

CardInvitationSpendingPeriod2

Bases: BaseModel

end_date instance-attribute
end_date

The end date (inclusive) of the spending period, in ISO 8601 ⧉ format (YYYY-MM-DD). Uses the timezone set by the business ⧉, or defaults to Europe/London.

end_date_action instance-attribute
end_date_action

The action to take after the end date of the spending period.

model_config class-attribute instance-attribute
model_config = ConfigDict(extra='allow')
start_date class-attribute instance-attribute
start_date = None

The start date (inclusive) of the spending period, in ISO 8601 ⧉ format (YYYY-MM-DD). Uses the timezone set by the business ⧉, or defaults to Europe/London.

CardInvitationSpendingPeriod3

Bases: BaseModel

model_config class-attribute instance-attribute
model_config = ConfigDict(extra='allow')

CardInvitationSpendingPeriod4

Bases: CardInvitationSpendingPeriod1, CardInvitationSpendingPeriod3

model_config class-attribute instance-attribute
model_config = ConfigDict(extra='allow')

CardInvitationSpendingPeriod5

Bases: CardInvitationSpendingPeriod2, CardInvitationSpendingPeriod3

model_config class-attribute instance-attribute
model_config = ConfigDict(extra='allow')

CardInvitationState

Bases: RootModel[Literal['created', 'expired', 'failed', 'redeemed']]

root instance-attribute
root

The current state of the card invitation: - created: Invitation has been created but not yet claimed. - expired: Invitation has expired due to expiry date being reached or manual cancellation. - failed: Invitation claim attempt failed. - redeemed: Invitation has been successfully claimed.

To learn more about card invitation lifecycle, see the guide: Manage card invitations → Card invitation state ⧉.

CardInvitationUpdatedAt

Bases: RootModel[AwareDatetime]

root instance-attribute
root

The date and time the card invitation was last updated in ISO 8601 ⧉ format.

CardInvitationUpdatedResponse

Bases: BaseModel

accounts instance-attribute
accounts
categories class-attribute instance-attribute
categories = None
countries class-attribute instance-attribute
countries = None
created_at instance-attribute
created_at
expiry_date class-attribute instance-attribute
expiry_date = None
holder_id class-attribute instance-attribute
holder_id = None
id instance-attribute
id
label class-attribute instance-attribute
label = None
merchant_controls class-attribute instance-attribute
merchant_controls = None
model_config class-attribute instance-attribute
model_config = ConfigDict(extra='allow')
spend_program class-attribute instance-attribute
spend_program = None
spending_limits class-attribute instance-attribute
spending_limits = None
spending_period class-attribute instance-attribute
spending_period = None
state instance-attribute
state
updated_at instance-attribute
updated_at
virtual instance-attribute
virtual

CardInvitationVirtual

Bases: RootModel[bool]

root instance-attribute
root

Specifies whether the issued card will be a virtual (true) or physical (false) one.

CardInvitationsResponse

Bases: RootModel[list[CardInvitationResponse]]

root instance-attribute
root

A list of card invitations

CardProduct

Bases: BaseModel

code instance-attribute
code

The code of the card product.

model_config class-attribute instance-attribute
model_config = ConfigDict(extra='allow')

CardReference

Bases: BaseModel

model_config class-attribute instance-attribute
model_config = ConfigDict(extra='allow')
name instance-attribute
name

The name of the card reference. Must be unique.

value instance-attribute
value

The value for this reference.

CardReferences

Bases: RootModel[list[CardReference]]

root instance-attribute
root

References for the card. Up to 5 name-value pairs assigned to the card for tracking.

Info

Each time the card is used, the references are recorded in the transaction details ⧉ (card.references), helping track transactions made with this card.

The names must be unique. The references can be amended ⧉ up to 10 times.

References are only supported for cards owned by the business (i.e. company ⧉ or auto-issued cards ⧉). They are not supported for team member cards ⧉ (i.e. with holder_id present).

Note

The references recorded on a transaction are those assigned to the card at the time the transaction took place. If the references are amended, they will only be applied to future transactions. Existing transaction are not affected.

CardResponse

Bases: BaseModel

accounts instance-attribute
accounts

The list of linked accounts.

can_be_unlocked class-attribute instance-attribute
can_be_unlocked = None
categories class-attribute instance-attribute
categories = None

The list of merchant categories that are available for card spending. If not specified, categories are not restricted.

contact_ids class-attribute instance-attribute
contact_ids = None
countries class-attribute instance-attribute
countries = None

The list of countries where the card can be used, specified as 2-letter ISO 3166 ⧉ codes.

created_at instance-attribute
created_at

The date and time the card was created in ISO 8601 ⧉ format.

expiry instance-attribute
expiry

The card expiration date.

holder_id class-attribute instance-attribute
holder_id = None

The ID of the team member who is the holder of the card. If the card belongs to the business, this will be empty.

For more information, see the guides: Manage Cards - Create a virtual card ⧉.

id instance-attribute
id

The ID of the card.

label class-attribute instance-attribute
label = None

The label of the card.

last_digits instance-attribute
last_digits

The last 4 digits of the card's PAN.

merchant_controls class-attribute instance-attribute
merchant_controls = None

The merchant-level controls for card spending.

They block or allow the card to only transact with specific merchants: - allow: permits only the specified merchants (cannot be used if the categories parameter is set) - block: blocks the specified merchants (can be used with or without categories)

model_config class-attribute instance-attribute
model_config = ConfigDict(extra='allow')
product class-attribute instance-attribute
product = None
references class-attribute instance-attribute
references = None
spend_program class-attribute instance-attribute
spend_program = None
spending_limits class-attribute instance-attribute
spending_limits = None
spending_period class-attribute instance-attribute
spending_period = None

The controls for the card's spending period.

They specify the dates when the card becomes available or unavailable for spending, and define what happens after the end date.

state instance-attribute
state
updated_at instance-attribute
updated_at

The date and time the card was last updated in ISO 8601 ⧉ format.

virtual instance-attribute
virtual

Specifies whether the card is virtual (true) or physical (false).

CardState

Bases: RootModel[Literal['created', 'pending', 'active', 'frozen', 'locked']]

root instance-attribute
root

The state that the card is in.

Possible values: - active: The card is available for spending. Newly created cards typically go into active unless subject to certain conditions, for example, spending period starting in the future. - frozen: The card has been frozen and is temporarily unavailable for spending. - locked: The card is locked, typically due to an admin lock ⧉ or spending period settings, i.e. when its spending_period.start_date is in the future or spending_period.end_date is in the past. A locked card is unavailable for spending until it's unlocked ⧉ and active.

Tip

To see if the card can be unlocked, check the can_be_unlocked parameter. Note that you'll still need the necessary scope or permission ⧉ to unlock it.

  • created: The card has been created but is not yet active. Used only for a specific type of cards.
  • pending: This status is currently not in use.

CardsResponse

Bases: RootModel[list[CardResponse]]

root instance-attribute
root

A list of cards.

Category

Bases: BaseModel

code class-attribute instance-attribute
code = None

The code of the category.

model_config class-attribute instance-attribute
model_config = ConfigDict(extra='allow')
name instance-attribute
name

The name of the category.

ChargeBearer

Bases: RootModel[Literal['shared', 'debtor']]

root instance-attribute
root

The party to which any transaction fees are charged if the resulting transaction route has associated fees. Some transactions with fees might not be possible with the specified option, in which case error 3287 is returned.

Counterparties

Bases: RootModel[list[Counterparty]]

root instance-attribute
root

Counterparty

Bases: BaseModel

accounts class-attribute instance-attribute
accounts = None

The list of public accounts associated with this counterparty.

cards class-attribute instance-attribute
cards = None

The list of cards associated with this counterparty.

country class-attribute instance-attribute
country = None
created_at instance-attribute
created_at

The date and time the counterparty was created in ISO 8601 ⧉ format.

id instance-attribute
id

The ID of the counterparty.

model_config class-attribute instance-attribute
model_config = ConfigDict(extra='allow')
name instance-attribute
name

The name of the counterparty.

profile_type class-attribute instance-attribute
profile_type = None
revtag class-attribute instance-attribute
revtag = None

The Revtag ⧉ of the counterparty.

state instance-attribute
state

Indicates the state of the counterparty.

updated_at instance-attribute
updated_at

The date and time the counterparty was last updated in ISO 8601 ⧉ format.

CounterpartyAccount

Bases: BaseModel

account_no class-attribute instance-attribute
account_no = None

The bank account number of the counterparty.

bank_country class-attribute instance-attribute
bank_country = None
bic class-attribute instance-attribute
bic = None

The BIC number of the counterparty's account if applicable.

bsb_code class-attribute instance-attribute
bsb_code = None

The BSB number of the counterparty's account if applicable.

clabe class-attribute instance-attribute
clabe = None

The CLABE number of the counterparty's account if applicable.

currency instance-attribute
currency
iban class-attribute instance-attribute
iban = None

The IBAN number of the counterparty's account if applicable.

id instance-attribute
id

The ID of the counterparty's account.

ifsc class-attribute instance-attribute
ifsc = None

The IFSC number of the counterparty's account if applicable.

model_config class-attribute instance-attribute
model_config = ConfigDict(extra='allow')
name class-attribute instance-attribute
name = None

The name of the counterparty.

recipient_charges class-attribute instance-attribute
recipient_charges = None

Indicates the possibility of the recipient charges.

Caution

This field is deprecated and should be disregarded. It is returned for legacy purposes only.

routing_number class-attribute instance-attribute
routing_number = None

The routing number of the counterparty's account if applicable.

sort_code class-attribute instance-attribute
sort_code = None

The sort code of the counterparty's account if applicable.

type instance-attribute
type

Indicates the type of account.

CounterpartyCard

Bases: BaseModel

country instance-attribute
country
currency instance-attribute
currency
id instance-attribute
id

The ID of the counterparty's card.

last_digits instance-attribute
last_digits

The last four digits of the card number.

model_config class-attribute instance-attribute
model_config = ConfigDict(extra='allow')
name instance-attribute
name

The name of the counterparty.

scheme instance-attribute
scheme

The card brand.

CounterpartyError

Bases: BaseModel

code instance-attribute
code

The error code.

message instance-attribute
message

The description of the error.

model_config class-attribute instance-attribute
model_config = ConfigDict(extra='allow')
params class-attribute instance-attribute
params = None

CounterpartyErrorParams

Bases: BaseModel

counterparty_id class-attribute instance-attribute
counterparty_id = None

The ID of the Revolut counterparty (i.e. internal counterparty) that already exists.

model_config class-attribute instance-attribute
model_config = ConfigDict(extra='allow')

Country

Bases: RootModel[str]

root instance-attribute
root

CountryCode

Bases: RootModel[str]

root instance-attribute
root

The bank country of the counterparty as 2-letter ISO 3166 ⧉ code.

CountryCodeCard

Bases: RootModel[str]

root instance-attribute
root

The country of the card issuer as 2-letter ISO 3166 ⧉ code.

CreateCounterpartyRequest

Bases: BaseModel

account_no class-attribute instance-attribute
account_no = None

The bank account number of the counterparty.

address class-attribute instance-attribute
address = None
bank_country class-attribute instance-attribute
bank_country = None
bic class-attribute instance-attribute
bic = None

The BIC number of the counterparty's account. This field is required for non-SEPA IBAN/SWIFT.

bsb_code class-attribute instance-attribute
bsb_code = None

The BSB number of the counterparty's account. This field is required for AUD accounts.

clabe class-attribute instance-attribute
clabe = None

The CLABE number of the counterparty's account. This field is required for SWIFT MX.

company_name class-attribute instance-attribute
company_name = None

The name of the company counterparty. Use when individual_name or name isn't specified and profile_type is business.

Caution

The company_name must contain at least 2 letters (not just characters).

For example, names like 12 will fail validation because they are two characters but not two letters.

currency class-attribute instance-attribute
currency = None
iban class-attribute instance-attribute
iban = None

The IBAN number of the counterparty's account. This field is displayed for IBAN countries.

ifsc class-attribute instance-attribute
ifsc = None

The IFSC number of the counterparty's account. This field is required for INR accounts.

individual_name class-attribute instance-attribute
individual_name = None
model_config class-attribute instance-attribute
model_config = ConfigDict(extra='allow')
name class-attribute instance-attribute
name = None

The name of the counterparty that you create for an existing Revolut user via Revtag. Provide the value only when you specify personal for profile_type.

profile_type class-attribute instance-attribute
profile_type = None
revtag class-attribute instance-attribute
revtag = None

The Revtag ⧉ of the counterparty to add.

routing_number class-attribute instance-attribute
routing_number = None

The routing number of the counterparty's account. This field is required for USD accounts.

sort_code class-attribute instance-attribute
sort_code = None

The sort code of the counterparty's account. This field is required for GBP accounts.

CreatePaymentDraftRequest

Bases: BaseModel

model_config class-attribute instance-attribute
model_config = ConfigDict(extra='allow')
payments instance-attribute
payments

The details of the payment(s) to be made.

schedule_for class-attribute instance-attribute
schedule_for = None

The scheduled date of the payment draft in ISO 8601 ⧉ format.

title class-attribute instance-attribute
title = None

The title of the payment draft.

CreatePaymentDraftResponse

Bases: BaseModel

id instance-attribute
id

The ID of the payment draft created.

model_config class-attribute instance-attribute
model_config = ConfigDict(extra='allow')

CreatePayoutLinkRequest

Bases: BaseModel

account_id instance-attribute
account_id
amount instance-attribute
amount
counterparty_name instance-attribute
counterparty_name
currency instance-attribute
currency
expiry_period class-attribute instance-attribute
expiry_period = None
model_config class-attribute instance-attribute
model_config = ConfigDict(extra='allow')
payout_methods instance-attribute
payout_methods
reference instance-attribute
reference
request_id instance-attribute
request_id

The ID of the request, provided by the sender.

Caution

To ensure that a link payment is not processed multiple times if there are network or system errors, the same request_id should be used for requests related to the same link.

save_counterparty class-attribute instance-attribute
save_counterparty = False

Indicates whether to save the recipient as your counterparty upon link claim. If false then the counterparty will not show up on your counterparties list, for example, when you retrieve your counterparties. However, you will still be able to retrieve this counterparty by its ID.

If you don't choose to save the counterparty on link creation, you can do it later from your transactions list in the Business app.

transfer_reason_code class-attribute instance-attribute
transfer_reason_code = None

CreateWebhookRequest

Bases: BaseModel

events instance-attribute
events

A list of event types to subscribe to. If you don't provide it, you're automatically subscribed to the default event types ⧉.

model_config class-attribute instance-attribute
model_config = ConfigDict(extra='allow')
url instance-attribute
url

A valid webhook URL to which to send event notifications. The supported protocol is https.

Currency

Bases: RootModel[str]

root instance-attribute
root

ISO 4217 ⧉ currency code in upper case.

CurrentChargeOptions

Bases: BaseModel

fee class-attribute instance-attribute
fee = None
from_ instance-attribute
from_
model_config class-attribute instance-attribute
model_config = ConfigDict(extra='allow')
rate class-attribute instance-attribute
rate = None
to instance-attribute
to

Error

Bases: BaseModel

code instance-attribute
code

The error code.

message instance-attribute
message

The description of the error.

model_config class-attribute instance-attribute
model_config = ConfigDict(extra='allow')

ErrorUnauthorized

Bases: ErrorWithStatus

model_config class-attribute instance-attribute
model_config = ConfigDict(extra='allow')

ErrorUnprocessableEntity

Bases: BaseModel

code class-attribute instance-attribute
code = None

The error code.

message instance-attribute
message

The description of the error.

model_config class-attribute instance-attribute
model_config = ConfigDict(extra='allow')

ErrorWithStatus

Bases: BaseModel

message instance-attribute
message

The description of the error.

model_config class-attribute instance-attribute
model_config = ConfigDict(extra='allow')
status instance-attribute
status

The error code.

EstimatedTime

Bases: BaseModel

max class-attribute instance-attribute
max = None

The maximum time estimate.

min class-attribute instance-attribute
min = None

The minimum time estimate.

model_config class-attribute instance-attribute
model_config = ConfigDict(extra='allow')
unit instance-attribute
unit

The estimated time unit of the inbound transfer of the funds.

ExchangePartFrom

Bases: BaseModel

account_id instance-attribute
account_id

The ID of the account to sell currency from.

amount class-attribute instance-attribute
amount = None

The amount of currency. Specify only if you want to sell currency.

currency instance-attribute
currency
model_config class-attribute instance-attribute
model_config = ConfigDict(extra='allow')

ExchangePartTo

Bases: BaseModel

account_id instance-attribute
account_id

The ID of the account to receive exchanged currency into.

amount class-attribute instance-attribute
amount = None

The amount of currency. Specify only if you want to buy currency.

currency instance-attribute
currency
model_config class-attribute instance-attribute
model_config = ConfigDict(extra='allow')

ExchangeRateResponse

Bases: BaseModel

fee instance-attribute
fee

The expected fee for the transaction.

from_ instance-attribute
from_

The money to sell.

model_config class-attribute instance-attribute
model_config = ConfigDict(extra='allow')
rate instance-attribute
rate

The proposed exchange rate.

rate_date instance-attribute
rate_date

The date of the proposed exchange rate in ISO 8601 ⧉ format.

to instance-attribute
to

The money to receive.

ExchangeReason

Bases: BaseModel

code instance-attribute
code

Category code of the reason for the exchange.

model_config class-attribute instance-attribute
model_config = ConfigDict(extra='allow')
name instance-attribute
name

Category name of the reason for the exchange.

ExchangeReasonCode

Bases: RootModel[str]

root instance-attribute
root

The reason code for the exchange. Depending on the country and the amount of funds to be exchanged, you might be required to provide an exchange reason. You can check available reason codes with the GET /exchange-reasons operation ⧉.

If an exchange reason is not required, this field is ignored.

ExchangeReasons

Bases: RootModel[list[ExchangeReason]]

root instance-attribute
root

The list of available exchange reasons.

ExchangeRequest

Bases: BaseModel

exchange_reason_code class-attribute instance-attribute
exchange_reason_code = None
from_ instance-attribute
from_
model_config class-attribute instance-attribute
model_config = ConfigDict(extra='allow')
reference class-attribute instance-attribute
reference = None

The reference for the exchange transaction, provided by you. It helps you to identify the transaction if you want to look it up later.

request_id instance-attribute
request_id

The ID of the request, provided by you. It helps you identify the transaction in your system.

Caution

To ensure that an exchange transaction is not processed multiple times if there are network or system errors, the same request_id should be used for requests related to the same transaction.

to instance-attribute
to

ExchangeResponse

Bases: BaseModel

completed_at class-attribute instance-attribute
completed_at = None

The date and time the transaction was completed in ISO 8601 ⧉ format.

created_at class-attribute instance-attribute
created_at = None

The date and time the transaction was created in ISO 8601 ⧉ format.

id class-attribute instance-attribute
id = None

The ID of the created transaction.

model_config class-attribute instance-attribute
model_config = ConfigDict(extra='allow')
reason_code class-attribute instance-attribute
reason_code = None

The reason why the transaction was failed or declined.

Present only when the state parameter of the transaction is declined or failed.

state class-attribute instance-attribute
state = None
type class-attribute instance-attribute
type = None

The type of the transaction. For money exchange, it is exchange.

Expense

Bases: BaseModel

completed_at class-attribute instance-attribute
completed_at = None

The date and time the expense was completed in ISO 8601 ⧉ format.

description class-attribute instance-attribute
description = None

The description of the expense.

expense_date instance-attribute
expense_date

The expense data depends on the type of the expense and whether it has been completed.

Typically, it's the date and time of the expense transaction ⧉ in ISO 8601 ⧉ format. - If the transaction related to the expense is completed, it is the date and time of its completion (completed_at ⧉). - Otherwise, it is the date and time of its creation (created_at ⧉).

For reimbursements, it's the payment date provided in the reimbursement request.

id instance-attribute
id

The ID of the expense.

labels instance-attribute
labels

The labels added to the expense, organised in groups.

You can have up to 5 label groups, with max. 1 label per group.

The labels are provided as an object, where each key is the name of a label group, and each value is a single-element array containing the selected label from that group.

merchant class-attribute instance-attribute
merchant = None

The name of the merchant.

model_config class-attribute instance-attribute
model_config = ConfigDict(extra='allow')
payer class-attribute instance-attribute
payer = None

The name of the team member ⧉ who made the transaction, refund request, or ATM withdrawal, or the name of the business if the related transaction is of type fee.

receipt_ids instance-attribute
receipt_ids

The IDs of the receipts related to the expense.

spent_amount instance-attribute
spent_amount

The expense amount in billed currency.

splits instance-attribute
splits

The splits of the expense.

A single expense can be divided into multiple parts (splits), for example, to allocate different portions of the expense to different categories.

state instance-attribute
state
submitted_at class-attribute instance-attribute
submitted_at = None

The date and time the expense was submitted in ISO 8601 ⧉ format.

transaction_id class-attribute instance-attribute
transaction_id = None

The ID of the transaction ⧉ related to the expense. Not available for transactions of type external.

transaction_type instance-attribute
transaction_type

ExpenseSplit

Bases: BaseModel

amount instance-attribute
amount

The original amount of the expense split.

category instance-attribute
category
model_config class-attribute instance-attribute
model_config = ConfigDict(extra='allow')
tax_rate instance-attribute
tax_rate

ExpenseState

Bases: RootModel[Literal['missing_info', 'awaiting_review', 'rejected', 'pending_reimbursement', 'refund_requested', 'refunded', 'approved', 'reverted']]

root instance-attribute
root

Indicates the state of the expense. Possible values:

  • missing_info: The expense is missing some required information.

    This is the initial state of the expense when it's first created. Once the missing information is provided, the expense is ready to be submitted.

  • awaiting_review: The expense is awaiting approval before it can be completed.

    The approver can approve, reject, or request refund for the expense. It is also possible for the submitter to undo the submission at this stage.

  • rejected: The expense has been rejected by the approver.

    The expense submitter (typically, the payer) should fix the issue that was the reason for the rejection and resubmit the expense.

  • pending_reimbursement: The reimbursement request has been approved, and the expense is awaiting reimbursement.

    This state is possible for reimbursements (transaction type = External).

  • refund_requested: The expense has been rejected and refund has been requested.*

    This state is possible for card transactions. It indicates that following the review, the approver rejected the expense and requested that it be refunded back to the business account. This can happen, for example, if an employee accidentally makes a personal purchase with their business card. Once refunded, the admin can mark this expense as refunded.

  • refunded: The expense has been refunded.*

    This state indicates that the admin has marked the expense as refunded.

  • approved: The expense has been approved and is now completed.*

  • reverted: The expense has been reverted.

    This status indicates that the transaction related to the expense has been reverted. In such a case, the expense status is automatically set to reverted, and the expense is completed. This can happen, for example, when the transaction has been reverted by the merchant.

*Additionally, if an admin has previously approved the expense, marked it as refunded/completed, or requested a refund, they can revert their decision. In such a case, the expense goes back to the initial missing_info state.

For more information, see the guides: Retrieve expenses and receipts ⧉.

ExpenseTransactionType

Bases: RootModel[Literal['atm', 'card_payment', 'fee', 'transfer', 'external', 'mileage_reimbursement']]

root instance-attribute
root

The type of the transaction ⧉ related to the expense.

Expenses

Bases: RootModel[list[Expense]]

root instance-attribute
root

Fee

Bases: BaseModel

amount class-attribute instance-attribute
amount = None

The fee amount.

currency class-attribute instance-attribute
currency = None
model_config class-attribute instance-attribute
model_config = ConfigDict(extra='allow')

IndividualName

Bases: BaseModel

first_name class-attribute instance-attribute
first_name = None

The first name of the individual counterparty.

last_name class-attribute instance-attribute
last_name = None

The last name of the individual counterparty.

model_config class-attribute instance-attribute
model_config = ConfigDict(extra='allow')

IndividualName1

Bases: BaseModel

first_name instance-attribute
first_name

The first name of the recipient.

last_name instance-attribute
last_name

The last name of the recipient.

model_config class-attribute instance-attribute
model_config = ConfigDict(extra='allow')

IndividualName7

Bases: BaseModel

first_name instance-attribute
first_name

The first name of the recipient.

last_name instance-attribute
last_name

The initial of the last name of the recipient.

model_config class-attribute instance-attribute
model_config = ConfigDict(extra='allow')

IndividualName8

Bases: BaseModel

first_name instance-attribute
first_name

The first name of the recipient.

last_name instance-attribute
last_name

The last name of the recipient.

model_config class-attribute instance-attribute
model_config = ConfigDict(extra='allow')

LabelGroup

Bases: RootModel[list[str]]

root instance-attribute
root

A label group with the selected label from that group.

MerchantControls

Bases: BaseModel

control_type instance-attribute
control_type

The type of control to apply.

merchant_ids instance-attribute
merchant_ids

The list of IDs of merchants to which the control applies.

Tip

To find merchant IDs, check transaction details (→ merchant.id). You can fetch transaction details for a specific transaction ⧉ or for all transactions ⧉.

model_config class-attribute instance-attribute
model_config = ConfigDict(extra='allow')

MerchantControlsSchema

Bases: BaseModel

control_type instance-attribute
control_type

The type of control to apply.

merchant_ids instance-attribute
merchant_ids

The list of IDs of merchants to which the control applies.

Tip

To find merchant IDs, check transaction details (→ merchant.id). You can fetch transaction details for a specific transaction ⧉ or for all transactions ⧉.

model_config class-attribute instance-attribute
model_config = ConfigDict(extra='allow')

PaymentDraftResponse

Bases: BaseModel

model_config class-attribute instance-attribute
model_config = ConfigDict(extra='allow')
payments instance-attribute
payments

The list of payments in the bulk.

scheduled_for class-attribute instance-attribute
scheduled_for = None

The scheduled date of the payment draft in ISO 8601 ⧉ format.

title class-attribute instance-attribute
title = None

The title of the payment draft.

PaymentDraftsResponse

Bases: BaseModel

model_config class-attribute instance-attribute
model_config = ConfigDict(extra='allow')
payment_orders instance-attribute
payment_orders

The list of payment drafts that haven't been sent for processing.

PaymentInfo

Bases: BaseModel

account_id instance-attribute
account_id

The ID of the account to pay from.

amount instance-attribute
amount
currency class-attribute instance-attribute
currency = None
current_charge_options instance-attribute
current_charge_options

The explanation of conversion process.

error_message class-attribute instance-attribute
error_message = None

The description of the error message.

id instance-attribute
id

The ID of the payment.

Do not confuse it with the payment draft ID ⧉ or transaction ID ⧉.

model_config class-attribute instance-attribute
model_config = ConfigDict(extra='allow')
reason class-attribute instance-attribute
reason = None

The reason for the current state.

receiver instance-attribute
receiver
reference class-attribute instance-attribute
reference = None

The description of the transaction.

state instance-attribute
state

PaymentOrderInfo

Bases: BaseModel

id instance-attribute
id

The ID of the payment draft.

model_config class-attribute instance-attribute
model_config = ConfigDict(extra='allow')
payments_count instance-attribute
payments_count

The number of payments in the payment draft.

scheduled_for class-attribute instance-attribute
scheduled_for = None

The scheduled date of the payment draft in ISO 8601 ⧉ format.

title class-attribute instance-attribute
title = None

The title of the payment draft.

PaymentReceiver

Bases: BaseModel

account_id class-attribute instance-attribute
account_id = None

The ID of the receiving counterparty's account. Used for bank transfers.

If the counterparty has multiple payment methods available, use it to specify the account to which you want to send the money.

card_id class-attribute instance-attribute
card_id = None

The ID of the receiving counterparty's card. Used for card transfers.

If the counterparty has multiple payment methods available, use it to specify the card to which you want to send the money.

counterparty_id instance-attribute
counterparty_id

The ID of the receiving counterparty.

model_config class-attribute instance-attribute
model_config = ConfigDict(extra='allow')

PaymentRequest

Bases: BaseModel

account_id instance-attribute
account_id

The ID of the account to pay from.

Note

You can specify only one account ID for multiple payments in the same payment draft.

amount instance-attribute
amount

The amount of the payment.

currency instance-attribute
currency
model_config class-attribute instance-attribute
model_config = ConfigDict(extra='allow')
receiver instance-attribute
receiver
reference instance-attribute
reference

The reference for the payment.

PaymentState

Bases: RootModel[Literal['CREATED', 'PENDING', 'COMPLETED', 'REVERTED', 'DECLINED', 'CANCELLED', 'FAILED', 'DELETED']]

root instance-attribute
root

Indicates the state of the transaction.

PaymentSystem

Bases: RootModel[Literal['chaps', 'bacs', 'faster_payments', 'sepa', 'swift', 'ach', 'elixir', 'sorbnet', 'nics', 'rix', 'sumclearing']]

root instance-attribute
root

Indicates the payment scheme used to execute transactions.

Bases: PayoutLinkInitialProps, PayoutLinkAdditionalProps

model_config class-attribute instance-attribute
model_config = ConfigDict(extra='allow')

PayoutLinkAccountId

Bases: RootModel[UUID]

root instance-attribute
root

The ID of the sender's account.

PayoutLinkAdditionalProps

Bases: BaseModel

cancellation_reason class-attribute instance-attribute
cancellation_reason = None

The reason for which the payout link was cancelled.

counterparty_id class-attribute instance-attribute
counterparty_id = None

The ID of the counterparty created based on the recipient's details.

Note

By default, the newly created counterparty is hidden from your counterparties list.

To automatically save it when the link is claimed, pass the save_counterparty parameter set to true.

Alternatively, you can add the recipient to your counterparties later from the list of transactions in the Business app.

model_config class-attribute instance-attribute
model_config = ConfigDict(extra='allow')
transaction_id class-attribute instance-attribute
transaction_id = None

The ID of the created transaction. Returned only if the payout has been claimed.

PayoutLinkAmount

Bases: RootModel[float]

root instance-attribute
root

The amount of money to be transferred.

Note

The amount must be between £1 and £2,500, or equivalent in the selected currency.

PayoutLinkCounterpartyName

Bases: RootModel[str]

root instance-attribute
root

The name of the counterparty provided by the sender.

PayoutLinkExpiryDate

Bases: RootModel[AwareDatetime]

root instance-attribute
root

The date and time after which the payout link expires in ISO 8601 format ⧉. If the recipient doesn't claim the money before then, the payout link expires and is no longer available.

The default and maximum value is the date and time of creating the link + 7 days.

PayoutLinkExpiryPeriod

Bases: RootModel[timedelta]

root instance-attribute
root

The period after which the payout link expires if not claimed before, provided in ISO 8601 format ⧉.

The default and maximum value is 7 days from the link creation.

PayoutLinkInitialProps

Bases: BaseModel

account_id instance-attribute
account_id
amount instance-attribute
amount
counterparty_name instance-attribute
counterparty_name
created_at instance-attribute
created_at

The date and time the payout link was created in ISO 8601 format ⧉.

currency instance-attribute
currency
expiry_date class-attribute instance-attribute
expiry_date = None
id instance-attribute
id

The ID of the payout link.

model_config class-attribute instance-attribute
model_config = ConfigDict(extra='allow')
payout_methods instance-attribute
payout_methods
reference instance-attribute
reference
request_id instance-attribute
request_id
save_counterparty instance-attribute
save_counterparty

Indicates whether you chose to save the recipient as your counterparty upon link claim. If false then the counterparty will not show up on your counterparties list, for example, when you retrieve your counterparties ⧉. However, you can still retrieve this counterparty by its ID ⧉.

If you didn't choose to save the counterparty on link creation, you can still do it from your transactions list in the Business app.

state instance-attribute
state
transfer_reason_code class-attribute instance-attribute
transfer_reason_code = None
updated_at instance-attribute
updated_at

The date and time the payout link was last updated in ISO 8601 format ⧉.

url class-attribute instance-attribute
url = None

The URL of the payout link. Returned only for active payout links.

PayoutLinkPayoutMethods

Bases: RootModel[list[PayoutMethod]]

root instance-attribute
root

The list of payout methods that the recipient can use to claim the payout, where: - revolut: Revolut peer-to-peer (P2P) transfer - bank_account: External bank transfer - card: Card transfer

PayoutLinkReference

Bases: RootModel[str]

root instance-attribute
root

The reference for the payout transaction, provided by the sender.

PayoutLinkRequestId

Bases: RootModel[str]

root instance-attribute
root

The ID of the request, provided by the sender.

PayoutLinkState

Bases: RootModel[Literal['created', 'failed', 'awaiting', 'active', 'expired', 'cancelled', 'processing', 'processed']]

root instance-attribute
root

The state that the payout link is in. Possible states are: - created: The payout link has been created, but the amount has not yet been blocked ⧉. - failed: The payout link couldn't be generated due to a failure during transaction booking. - awaiting: The payout link is awaiting approval. - active: The payout link can be redeemed. - expired: The payout link cannot be redeemed because it wasn't claimed before its expiry date. - cancelled: The payout link cannot be redeemed because it was cancelled. - processing: The payout link has been redeemed and is being processed. - processed: The payout link has been redeemed and the money has been transferred to the recipient.

Bases: RootModel[list[PayoutLink]]

root instance-attribute
root

A list of payout links

PayoutMethod

Bases: RootModel[Literal['revolut', 'bank_account', 'card']]

root instance-attribute
root

The payout method that the recipient can use to claim the payout.

ProfileType

Bases: RootModel[Literal['personal', 'business']]

root instance-attribute
root

The type of the Revolut profile. Used when adding an existing Revolut user via Revtag.

Role

Bases: BaseModel

created_at instance-attribute
created_at

The date and time the role was created in ISO 8601 ⧉ format.

id instance-attribute
id

The ID of the role. This can be a UUID or other default role such as OWNER.

model_config class-attribute instance-attribute
model_config = ConfigDict(extra='allow')
name instance-attribute
name

The name of the role.

updated_at instance-attribute
updated_at

The date and time the role was last updated in ISO 8601 ⧉ format.

SpendProgram

Bases: BaseModel

label instance-attribute
label

The name of the spend program.

model_config class-attribute instance-attribute
model_config = ConfigDict(extra='allow')

SpendingLimitPeriodic

Bases: BaseModel

amount instance-attribute
amount

The value of the spending limit.

currency instance-attribute
currency
model_config class-attribute instance-attribute
model_config = ConfigDict(extra='allow')

SpendingLimitSingleTransaction

Bases: BaseModel

amount instance-attribute
amount

The value of the spending limit.

currency instance-attribute
currency
model_config class-attribute instance-attribute
model_config = ConfigDict(extra='allow')

SpendingLimits

Bases: BaseModel

all_time class-attribute instance-attribute
all_time = None

The all-time limit for transactions.

day class-attribute instance-attribute
day = None

The daily limit for transactions.

model_config class-attribute instance-attribute
model_config = ConfigDict(extra='allow')
month class-attribute instance-attribute
month = None

The monthly limit for transactions.

quarter class-attribute instance-attribute
quarter = None

The quarterly limit for transactions.

single class-attribute instance-attribute
single = None

The limit for a single transaction.

week class-attribute instance-attribute
week = None

The weekly limit for transactions.

year class-attribute instance-attribute
year = None

The yearly limit for transactions.

SpendingLimitsSchema

Bases: BaseModel

all_time class-attribute instance-attribute
all_time = None

The all-time limit for transactions.

day class-attribute instance-attribute
day = None

The daily limit for transactions.

model_config class-attribute instance-attribute
model_config = ConfigDict(extra='allow')
month class-attribute instance-attribute
month = None

The monthly limit for transactions.

quarter class-attribute instance-attribute
quarter = None

The quarterly limit for transactions.

single class-attribute instance-attribute
single = None

The limit for a single transaction.

week class-attribute instance-attribute
week = None

The weekly limit for transactions.

year class-attribute instance-attribute
year = None

The yearly limit for transactions.

SpendingPeriodSchema

Bases: RootModel[SpendingPeriodSchema1 | SpendingPeriodSchema2]

root instance-attribute
root

SpendingPeriodSchema1

Bases: BaseModel

end_date class-attribute instance-attribute
end_date = None

The end date (inclusive) of the spending period, in ISO 8601 ⧉ format (YYYY-MM-DD). Uses the timezone set by the business ⧉, or defaults to Europe/London.

end_date_action class-attribute instance-attribute
end_date_action = None

The action to take after the end date of the spending period.

model_config class-attribute instance-attribute
model_config = ConfigDict(extra='allow')
start_date instance-attribute
start_date

The start date (inclusive) of the spending period, in ISO 8601 ⧉ format (YYYY-MM-DD). Uses the timezone set by the business ⧉, or defaults to Europe/London.

SpendingPeriodSchema2

Bases: BaseModel

end_date instance-attribute
end_date

The end date (inclusive) of the spending period, in ISO 8601 ⧉ format (YYYY-MM-DD). Uses the timezone set by the business ⧉, or defaults to Europe/London.

end_date_action instance-attribute
end_date_action

The action to take after the end date of the spending period.

model_config class-attribute instance-attribute
model_config = ConfigDict(extra='allow')
start_date class-attribute instance-attribute
start_date = None

The start date (inclusive) of the spending period, in ISO 8601 ⧉ format (YYYY-MM-DD). Uses the timezone set by the business ⧉, or defaults to Europe/London.

SpentAmount

Bases: BaseModel

amount instance-attribute
amount
currency instance-attribute
currency
model_config class-attribute instance-attribute
model_config = ConfigDict(extra='allow')

TaxRate

Bases: BaseModel

model_config class-attribute instance-attribute
model_config = ConfigDict(extra='allow')
name instance-attribute
name

The name of the tax.

percentage class-attribute instance-attribute
percentage = None

The tax rate percentage applied to the taxable amount. For example, 23 for 23%.

TeamMember

Bases: BaseModel

created_at instance-attribute
created_at

The date and time the team member was created in ISO 8601 ⧉ format.

email instance-attribute
email

The email of the team member.

first_name class-attribute instance-attribute
first_name = None

The team member's first name.

id instance-attribute
id

The ID of the team member.

last_name class-attribute instance-attribute
last_name = None

The team member's last name.

model_config class-attribute instance-attribute
model_config = ConfigDict(extra='allow')
role_id instance-attribute
role_id

The ID of the team member's role ⧉. This can be a UUID or other default role such as Owner.

state instance-attribute
state
updated_at instance-attribute
updated_at

The date and time the team member was last updated in ISO 8601 ⧉ format.

TeamMemberState

Bases: RootModel[Literal['created', 'confirmed', 'waiting', 'active', 'locked', 'disabled']]

root instance-attribute
root

The state that the team member is in.

Transaction

Bases: BaseModel

card class-attribute instance-attribute
card = None
completed_at class-attribute instance-attribute
completed_at = None

The date and time the transaction was completed in ISO 8601 ⧉ format. This is required when the transaction state is completed.

created_at instance-attribute
created_at

The date and time the transaction was created in ISO 8601 ⧉ format.

id instance-attribute
id

The ID of the transaction.

legs instance-attribute
legs

The legs of the transaction: - For transactions between your Revolut accounts, there can be 2 legs, for example, an internal transfer made out of the GBP account and into the EUR account. - For transactions in other cases, there is only 1 leg.

merchant class-attribute instance-attribute
merchant = None
model_config class-attribute instance-attribute
model_config = ConfigDict(extra='allow')
reason_code class-attribute instance-attribute
reason_code = None

The reason code when the transaction state is declined or failed.

reference class-attribute instance-attribute
reference = None

The payment reference.

related_transaction_id class-attribute instance-attribute
related_transaction_id = None

The ID of the original transaction to which this transaction is related. Returned, for example, when this transaction is a refund of the related transaction, or for transactions related to cashback.

request_id class-attribute instance-attribute
request_id = None

The request ID that you provided previously.

scheduled_for class-attribute instance-attribute
scheduled_for = None

The scheduled date of the payment, if applicable. Provided in ISO 8601 ⧉ format.

state instance-attribute
state
type instance-attribute
type
updated_at instance-attribute
updated_at

The date and time the transaction was last updated in ISO 8601 ⧉ format.

TransactionCard

Bases: BaseModel

card_number class-attribute instance-attribute
card_number = None

The masked card number.

first_name class-attribute instance-attribute
first_name = None

The first name of the cardholder.

id class-attribute instance-attribute
id = None

The ID of the card.

last_name class-attribute instance-attribute
last_name = None

The last name of the cardholder.

model_config class-attribute instance-attribute
model_config = ConfigDict(extra='allow')
phone class-attribute instance-attribute
phone = None

The phone number of the cardholder in E.164 ⧉ format.

references class-attribute instance-attribute
references = None

Card references (references ⧉).

Note

These are the references that were assigned to the card at the time the transaction was made. Any update ⧉ to card references does not affect existing transactions.

TransactionCounterparty

Bases: BaseModel

account_id class-attribute instance-attribute
account_id = None

The ID of the counterparty account.

account_type instance-attribute
account_type
id class-attribute instance-attribute
id = None

The ID of the counterparty.

model_config class-attribute instance-attribute
model_config = ConfigDict(extra='allow')

TransactionCounterpartyAccountType

Bases: RootModel[Literal['self', 'revolut', 'external']]

root instance-attribute
root

Indicates the type of the account.

TransactionLeg

Bases: BaseModel

account_id instance-attribute
account_id

The ID of the account that the transaction is associated with.

amount instance-attribute
amount

The amount of the transaction.

balance class-attribute instance-attribute
balance = None

The total balance of the account that the transaction is associated with.

bill_amount class-attribute instance-attribute
bill_amount = None

The billing amount for cross-currency payments.

bill_currency class-attribute instance-attribute
bill_currency = None
counterparty class-attribute instance-attribute
counterparty = None
currency instance-attribute
currency
description class-attribute instance-attribute
description = None

The transaction leg purpose.

fee class-attribute instance-attribute
fee = None

The amount of the transaction fee.

leg_id instance-attribute
leg_id

The ID of the leg.

model_config class-attribute instance-attribute
model_config = ConfigDict(extra='allow')

TransactionLimitCurrency

Bases: RootModel[str]

root instance-attribute
root

The currency of the spending limit, provided as ISO 4217 ⧉ code in upper case.

TransactionMerchant

Bases: BaseModel

category_code class-attribute instance-attribute
category_code = None

The category code of the merchant.

city class-attribute instance-attribute
city = None

The city of the merchant.

country class-attribute instance-attribute
country = None
id class-attribute instance-attribute
id = None

The ID of the merchant.

model_config class-attribute instance-attribute
model_config = ConfigDict(extra='allow')
name class-attribute instance-attribute
name = None

The name of the merchant.

TransactionPaymentRequest

Bases: BaseModel

account_id instance-attribute
account_id

The ID of the account that you send the funds from.

amount instance-attribute
amount

The amount to transfer.

charge_bearer class-attribute instance-attribute
charge_bearer = None
currency class-attribute instance-attribute
currency = None
exchange_reason_code class-attribute instance-attribute
exchange_reason_code = None
model_config class-attribute instance-attribute
model_config = ConfigDict(extra='allow')
receiver instance-attribute
receiver
reference class-attribute instance-attribute
reference = None

The reference for the transaction.

request_id instance-attribute
request_id

The ID of the transaction, provided by you.

Caution

Always provide a unique request ID for each individual payment. This allows you to safely retry the payment in the event of any network issues; if the payment was successful, a second attempt with the same request ID won't be processed.

transfer_reason_code class-attribute instance-attribute
transfer_reason_code = None

TransactionState

Bases: RootModel[Literal['created', 'pending', 'completed', 'declined', 'failed', 'reverted']]

root instance-attribute
root

Indicates the transaction state. Possible values: - created: The transaction has been created and is either processed asynchronously or scheduled for a later time. - pending: The transaction is pending until it's being processed. If the transfer is made between Revolut accounts, this state is skipped and the transaction is executed instantly. - completed: The transaction was successful. - declined: The transaction was unsuccessful. This can happen for a variety of reasons, for example, insufficient account balance, wrong receiver information, etc. - failed: The transaction was unsuccessful. This can happen for a variety of reasons, for example, invalid API calls, blocked payments, etc. - reverted: The transaction was reverted. This can happen for a variety of reasons, for example, the receiver being inaccessible.

TransactionType

Bases: RootModel[Literal['atm', 'card_payment', 'card_refund', 'card_chargeback', 'card_credit', 'exchange', 'transfer', 'loan', 'fee', 'refund', 'topup', 'topup_return', 'tax', 'tax_refund']]

root instance-attribute
root

Indicates the transaction type.

Transactions

Bases: RootModel[list[Transaction]]

root instance-attribute
root

TransferReason

Bases: BaseModel

code instance-attribute
code

Category name of the transfer reason.

country instance-attribute
country
currency instance-attribute
currency
description instance-attribute
description

The description of the given transfer reason.

model_config class-attribute instance-attribute
model_config = ConfigDict(extra='allow')

TransferReasonCode

Bases: RootModel[str]

root instance-attribute
root

The reason code for the transaction. Transactions to certain countries and currencies might require you to provide a transfer reason. You can check available reason codes with the GET /transfer-reasons operation ⧉.

If a transfer reason is not required for the given currency and country, this field is ignored.

TransferReasons

Bases: RootModel[list[TransferReason]]

root instance-attribute
root

TransferRequest

Bases: BaseModel

amount instance-attribute
amount

The amount of the funds to be transferred.

currency instance-attribute
currency
model_config class-attribute instance-attribute
model_config = ConfigDict(extra='allow')
reference class-attribute instance-attribute
reference = None

The reference for the funds transfer.

request_id instance-attribute
request_id

The ID of the request, provided by you. It helps you identify the transaction in your system.

Caution

To ensure that a transfer is not processed multiple times if there are network or system errors, the same request_id should be used for requests related to the same transfer.

source_account_id instance-attribute
source_account_id

The ID of the source account that you transfer the funds from.

target_account_id instance-attribute
target_account_id

The ID of the target account that you transfer the funds to.

TransferResponse

Bases: BaseModel

completed_at class-attribute instance-attribute
completed_at = None

The date and time the transaction was completed in ISO 8601 ⧉ format.

created_at instance-attribute
created_at

The date and time the transaction was created in ISO 8601 ⧉ format.

id instance-attribute
id

The ID of the transaction created.

model_config class-attribute instance-attribute
model_config = ConfigDict(extra='allow')
state instance-attribute
state

UpdateWebhookRequest

Bases: BaseModel

events class-attribute instance-attribute
events = None

A list of event types to subscribe to.

model_config class-attribute instance-attribute
model_config = ConfigDict(extra='allow')
url class-attribute instance-attribute
url = None

A valid webhook URL to which to send event notifications. The supported protocol is https.

Url

Bases: BaseModel

model_config class-attribute instance-attribute
model_config = ConfigDict(extra='allow')
url instance-attribute
url

ValidateAccountNameRequest

ValidateAccountNameRequestAU

Bases: BaseModel

account_no instance-attribute
account_no

The account number of the counterparty.

bsb instance-attribute
bsb

The BSB (Bank-State-Branch) number for the counterparty's account. Used to identify the bank and branch in Australia.

company_name class-attribute instance-attribute
company_name = None

The name of the business counterparty.

Required when the account type is business (individual_name is not specified).

individual_name class-attribute instance-attribute
individual_name = None

The name of the individual counterparty, split into first name and last name.

Required when the account type is personal (company_name isn't specified).

model_config class-attribute instance-attribute
model_config = ConfigDict(extra='allow')

ValidateAccountNameRequestEUR

Bases: BaseModel

bic class-attribute instance-attribute
bic = None

The BIC (Bank Identifier Code) for the counterparty's account. Also known as the SWIFT code. It can be provided, for example, when the automatic BIC detection fails and the check is unsuccessful.

company_name class-attribute instance-attribute
company_name = None

The name of the business counterparty.

Required when the account type is business (individual_name is not specified).

iban instance-attribute
iban

The IBAN (International Bank Account Number) for the counterparty's account.

individual_name class-attribute instance-attribute
individual_name = None

The name of the individual counterparty, split into first name and last name.

Required when the account type is personal (company_name isn't specified).

model_config class-attribute instance-attribute
model_config = ConfigDict(extra='allow')
recipient_country instance-attribute
recipient_country

The counterparty's bank country, provided as a 2-letter ISO 3166 ⧉ code.

recipient_currency instance-attribute
recipient_currency

The counterparty account’s currency, provided as a 3-letter ISO 4217 ⧉ code.

For VoP, the possible value is EUR.

ValidateAccountNameRequestRO

Bases: BaseModel

bic class-attribute instance-attribute
bic = None

The BIC (Bank Identifier Code) for the counterparty's account. Also known as the SWIFT code. This value is optional. It can be provided, for example, when the automatic BIC detection fails and the check is unsuccessful.

company_name class-attribute instance-attribute
company_name = None

The name of the business counterparty.

Required when the account type is business (individual_name is not specified).

ⓘ Note that for RO ⧉, the name you provide helps identify the request, but is not used for the actual check. The API simply returns the partially masked name associated with the IBAN for you to validate. Therefore, the returned name may differ from the one you provide.

iban instance-attribute
iban

The IBAN (International Bank Account Number) for the counterparty's account.

individual_name class-attribute instance-attribute
individual_name = None

The name of the individual counterparty, split into first name and last name.

Required when the account type is personal (company_name isn't specified).

ⓘ Note that for RO ⧉, the name you provide helps identify the request, but is not used for the actual check. The API simply returns the partially masked name associated with the IBAN for you to validate. Therefore, the returned name may differ from the one you provide.

model_config class-attribute instance-attribute
model_config = ConfigDict(extra='allow')
recipient_country instance-attribute
recipient_country

The counterparty's bank country, provided as a 2-letter ISO 3166 ⧉ code.

For RO CoP, the possible value is RO.

recipient_currency instance-attribute
recipient_currency

The counterparty account’s currency, provided as a 3-letter ISO 4217 ⧉ code.

For RO CoP, the possible value is RON.

ValidateAccountNameRequestUK

Bases: BaseModel

account_no instance-attribute
account_no

The account number of the counterparty.

company_name class-attribute instance-attribute
company_name = None

The name of the business counterparty.

Required when the account type is business (individual_name is not specified).

individual_name class-attribute instance-attribute
individual_name = None

The name of the individual counterparty, split into first name and last name.

Required when the account type is personal (company_name isn't specified).

model_config class-attribute instance-attribute
model_config = ConfigDict(extra='allow')
sort_code instance-attribute
sort_code

The sort code of the counterparty's account.

ValidateAccountNameResponse

ValidateAccountNameResponseAU

Bases: BaseModel

company_name class-attribute instance-attribute
company_name = None

The name of the recipient when the account type is business. Provided only if individual_name is not specified.

When the name is a close match, the actual full name is returned. Otherwise, the name you provided is returned.

Mismatched name type is corrected

The mismatched name type in the request is corrected in the response. This means that, for example, if an individual recipient's name was provided under company_name, in the response it is returned under individual_name.

individual_name class-attribute instance-attribute
individual_name = None

The name of the recipient when the account type is personal. Provided only if company_name is not specified.

When the name is a close match, the actual first and last names are returned. Otherwise, the name you provided is returned.

Mismatched name type is corrected

The mismatched name type in the request is corrected in the response. This means that, for example, if an individual recipient's name was provided under company_name, in the response it is returned under individual_name.

model_config class-attribute instance-attribute
model_config = ConfigDict(extra='allow')
reason class-attribute instance-attribute
reason = None
result_code instance-attribute
result_code

The result of the check.

Possible values for AU: - matched: The name matches the provided details. For personal accounts, this also covers close matches, in which case the actual name is returned. - close_match (business account): The name is similar to the provided value. - not_matched: The name doesn't match the provided values. - cannot_be_checked: The check cannot be performed and retries won't help. For example, the recipient's bank doesn't support CoP. - temporarily_unavailable: The check cannot be performed right now. For example, the recipient's bank didn't respond to our request. You should retry the request later.

ValidateAccountNameResponseEUR

Bases: BaseModel

company_name class-attribute instance-attribute
company_name = None

The name of the recipient when the account type is business. Provided only if individual_name is not specified.

When the name is a close match, the actual name is returned. Otherwise, the name you provided is returned.

Mismatched name type is not corrected

The name type in the response is returned as it was provided in the request. For example, if an individual recipient's name was provided under company_name, in the response it's still returned under company_name (instead of individual_name).

individual_name class-attribute instance-attribute
individual_name = None

The name of the recipient when the account type is personal. Provided only if company_name is not specified.

When the name is a close match, the actual name is returned. Otherwise, the name you provided is returned.

Mismatched name type is not corrected

The name type in the response is returned as it was provided in the request. For example, if an individual recipient's name was provided under company_name, in the response it's still returned under company_name (instead of individual_name).

model_config class-attribute instance-attribute
model_config = ConfigDict(extra='allow')
reason class-attribute instance-attribute
reason = None
result_code instance-attribute
result_code

The result of the check.

Possible values for EUR: - matched: The name matches the provided details. - close_match: The recipient's name is similar to the provided value. The actual name is returned. - not_matched: The name and account type don't match the provided values. The name provided in the request is returned. - cannot_be_checked: The check cannot be performed and retries won't help. For example, the recipient's bank doesn't support VoP. - temporarily_unavailable: The check cannot be performed right now. For example, the recipient's bank didn't respond to our request. You should retry the request later.

ValidateAccountNameResponseRO

Bases: BaseModel

company_name class-attribute instance-attribute
company_name = None

The partially masked name of the recipient when the account type is business. Provided only if individual_name is not specified.

Name considerations

  • The name you provide helps identify the request, but is not used for the actual check. The API simply returns the partial name associated with the IBAN for you to validate. Therefore, the returned name may differ from the one you provided.
  • Mismatched name type is not corrected. This means that the name type in the response is returned as it was provided in the request. For example, if an individual recipient's name was provided under company_name, in the response it's still returned under company_name (instead of individual_name).
individual_name class-attribute instance-attribute
individual_name = None

The partial name of the recipient when the account type is personal, that is, the first name and the last name's initial. Provided only if company_name is not specified.

Name considerations

  • The name you provide helps identify the request, but is not used for the actual check. The API simply returns the partial name associated with the IBAN for you to validate. Therefore, the returned name may differ from the one you provided.
  • Mismatched name type is not corrected. This means that the name type in the response is returned as it was provided in the request. For example, if an individual recipient's name was provided under company_name, in the response it's still returned under company_name (instead of individual_name).
model_config class-attribute instance-attribute
model_config = ConfigDict(extra='allow')
reason class-attribute instance-attribute
reason = None
result_code instance-attribute
result_code

The result of the check.

For RO CoP, the API checks if an account exists for the provided IBAN. Possible results are: - matched: An account with the provided IBAN was found. When this status is returned, the API also returns a partial name of the account holder. Use this returned name to validate your recipient's details. - cannot_be_checked: The check cannot be performed and retries won't help. For example, the recipient's bank doesn't support CoP. - temporarily_unavailable: The check cannot be performed right now. For example, the recipient's bank didn't respond to our request. You should retry the request later.

ValidateAccountNameResponseUK

Bases: BaseModel

company_name class-attribute instance-attribute
company_name = None

The name of the recipient when the account type is business. Provided only if individual_name is not specified.

When the name is a close match, the actual full name is returned, otherwise, the name you provided is returned.

individual_name class-attribute instance-attribute
individual_name = None

The name of the recipient when the account type is personal. Provided only if company_name is not specified.

When the name is a close match, the actual first and last names are returned in full.

model_config class-attribute instance-attribute
model_config = ConfigDict(extra='allow')
reason class-attribute instance-attribute
reason = None
result_code instance-attribute
result_code

The result of the check.

Possible values for UK: - matched: The name and account type match the provided details. - close_match: The name and/or account type are similar to the provided values: - The name is a match, but the account type is incorrect. For example, an individual recipient's name was provided under company_name. The actual account type is returned. - The name is similar to the provided values. Account type is correct. The actual name is returned. - The name is similar to the provided values, and the account type is incorrect. The actual values are returned.

The actual values are returned. - not_matched: The name and account type don't match the provided values. - cannot_be_checked: The check cannot be performed and retries won't help. For example, the recipient's bank doesn't support CoP. - temporarily_unavailable: The check cannot be performed right now. For example, the recipient's bank didn't respond to our request. You should retry the request later.

WebhookEvent

Bases: BaseModel

created_at instance-attribute
created_at

The date and time the event was created in ISO 8601 ⧉ format.

id instance-attribute
id

The ID of the webhook event.

last_sent_date class-attribute instance-attribute
last_sent_date = None

The date and time the last attempt at the event delivery occurred in ISO 8601 ⧉ format.

model_config class-attribute instance-attribute
model_config = ConfigDict(extra='allow')
payload instance-attribute
payload

The details of the failed event.

updated_at instance-attribute
updated_at

The date and time the event was last updated in ISO 8601 ⧉ format.

webhook_id instance-attribute
webhook_id

The ID of the webhook for which the event failed.

webhook_url instance-attribute
webhook_url

The valid webhook URL that event notifications are sent to. The supported protocol is https.

WebhookEventType

Bases: RootModel[Literal['TransactionCreated', 'TransactionStateChanged', 'PayoutLinkCreated', 'PayoutLinkStateChanged']]

root instance-attribute
root

The type of the webhook event to subscribe to.

WebhookEvents

Bases: RootModel[list[WebhookEvent]]

root instance-attribute
root

WebhookSigningSecretRotateRequest

Bases: BaseModel

expiration_period class-attribute instance-attribute
expiration_period = None

The expiration period for the signing secret in ISO 8601 format ⧉. If set, when you rotate the secret, it continues to be valid until the expiration period has passed. Otherwise, on rotation, the secret is invalidated immediately. The maximum value is 7 days.

model_config class-attribute instance-attribute
model_config = ConfigDict(extra='allow')

WebhookV1

Bases: BaseModel

model_config class-attribute instance-attribute
model_config = ConfigDict(extra='allow')
url instance-attribute
url

The valid webhook URL that event notifications are sent to. The supported protocol is https.

WebhookV2

Bases: BaseModel

events instance-attribute
events

The list of event types that you are subscribed to.

id instance-attribute
id

The ID of the webhook.

model_config class-attribute instance-attribute
model_config = ConfigDict(extra='allow')
signing_secret instance-attribute
signing_secret

The signing secret for the webhook.

url instance-attribute
url

The valid webhook URL that event notifications are sent to. The supported protocol is https.

WebhookV2Basic

Bases: BaseModel

events instance-attribute
events

The list of event types that you are subscribed to.

id instance-attribute
id

The ID of the webhook.

model_config class-attribute instance-attribute
model_config = ConfigDict(extra='allow')
url instance-attribute
url

The valid webhook URL that event notifications are sent to. The supported protocol is https.

Webhooks

Bases: RootModel[list[WebhookV2Basic]]

root instance-attribute
root

fix_openapi_spec

Fix invalid OpenAPI spec issues for datamodel-codegen compatibility.

Fixes: - Removes invalid minimum/maximum constraints from string-typed fields (these constraints are only valid for numeric types in JSON Schema) - Fixes invalid discriminator definitions that lack propertyName and oneOf - Converts ::: admonition syntax to MkDocs Material !!! syntax

Usage

python fix_openapi_spec.py input.yaml > output.yaml python fix_openapi_spec.py input.yaml | datamodel-codegen --input-file-type openapi ...

convert_admonitions

convert_admonitions(text)

Convert ::: admonition syntax to MkDocs Material !!! syntax.

Handles: - :::note ... ::: -> !!! note\n ... - ::::::note ... :::::: (nested containers) -> flattened !!! note - Nested admonitions are flattened to the same level

Source code in shared/services/payment_providers/revolut/openapi/fix_openapi_spec.py
def convert_admonitions(text: str) -> str:
    """
    Convert ::: admonition syntax to MkDocs Material !!! syntax.

    Handles:
    - :::note ... ::: -> !!! note\\n    ...
    - ::::::note ... :::::: (nested containers) -> flattened !!! note
    - Nested admonitions are flattened to the same level
    """
    if ":::" not in text:
        return text

    lines = text.split("\n")
    result_lines: list[str] = []
    current_admonition: str | None = None
    current_title: str | None = None
    content_buffer: list[str] = []

    # Pattern to match admonition start: :::+ followed by type and optional [title]
    # e.g., :::note, ::::::caution, :::tip[Custom title]
    admonition_start_pattern = re.compile(r"^(:+)(\w+)(?:\[([^\]]+)\])?\s*$")
    # Pattern to match admonition end: just colons (:::, ::::::, etc.)
    admonition_end_pattern = re.compile(r"^(:+)\s*$")

    def flush_admonition() -> None:
        """Flush current admonition content to result."""
        nonlocal current_admonition, current_title, content_buffer
        if current_admonition:
            if current_title:
                result_lines.append(f'!!! {current_admonition} "{current_title}"')
            else:
                result_lines.append(f"!!! {current_admonition}")
            for line in content_buffer:
                if line.strip():
                    result_lines.append(f"    {line}")
                else:
                    result_lines.append("")
            # Remove trailing empty lines from the admonition
            while result_lines and result_lines[-1] == "":
                result_lines.pop()
            result_lines.append("")  # Add one blank line after admonition
        current_admonition = None
        current_title = None
        content_buffer = []

    for line in lines:
        stripped = line.strip()

        # Check for admonition start
        start_match = admonition_start_pattern.match(stripped)
        if start_match:
            # Flush any previous admonition
            flush_admonition()
            current_admonition = start_match.group(2)
            current_title = start_match.group(3)  # May be None if no [title]
            continue

        # Check for admonition end (just colons) - always skip these lines
        end_match = admonition_end_pattern.match(stripped)
        if end_match:
            # Flush current admonition if we're inside one
            if current_admonition:
                flush_admonition()
            # Skip the end marker line entirely
            continue

        # Regular content
        if current_admonition:
            content_buffer.append(line)
        else:
            result_lines.append(line)

    # Flush any remaining admonition
    flush_admonition()

    # Clean up multiple consecutive blank lines
    cleaned_lines: list[str] = []
    prev_blank = False
    for line in result_lines:
        is_blank = line.strip() == ""
        if is_blank and prev_blank:
            continue
        cleaned_lines.append(line)
        prev_blank = is_blank

    return "\n".join(cleaned_lines).strip()

fix_invalid_discriminator

fix_invalid_discriminator(obj)

Fix invalid discriminator definitions.

OpenAPI 3.0 discriminators must have a propertyName and be used with oneOf/anyOf. If we find a discriminator with only mapping (no propertyName), convert it to oneOf.

Source code in shared/services/payment_providers/revolut/openapi/fix_openapi_spec.py
def fix_invalid_discriminator(obj: dict[str, Any]) -> None:
    """
    Fix invalid discriminator definitions.

    OpenAPI 3.0 discriminators must have a propertyName and be used with oneOf/anyOf.
    If we find a discriminator with only mapping (no propertyName), convert it to oneOf.
    """
    if "discriminator" not in obj:
        return

    discriminator = obj["discriminator"]
    if not isinstance(discriminator, dict):
        return

    # Check if discriminator has mapping but no propertyName (invalid)
    if "mapping" in discriminator and "propertyName" not in discriminator:
        mapping = discriminator["mapping"]
        if isinstance(mapping, dict):
            # Convert to oneOf pattern
            one_of_refs = [{"$ref": ref} for ref in mapping.values()]
            obj["type"] = "object"
            obj["oneOf"] = one_of_refs
            del obj["discriminator"]

fix_openapi_spec

fix_openapi_spec(spec)

Fix the entire OpenAPI spec.

Source code in shared/services/payment_providers/revolut/openapi/fix_openapi_spec.py
def fix_openapi_spec(spec: dict[str, Any]) -> dict[str, Any]:
    """Fix the entire OpenAPI spec."""
    fix_schema_object(spec)
    fix_string_values(spec)
    return spec

fix_schema_object

fix_schema_object(obj)

Recursively fix schema objects by removing invalid constraints.

Removes minimum/maximum from string-typed fields since these constraints are only valid for numeric types in JSON Schema.

Source code in shared/services/payment_providers/revolut/openapi/fix_openapi_spec.py
def fix_schema_object(obj: Any) -> None:
    """
    Recursively fix schema objects by removing invalid constraints.

    Removes `minimum`/`maximum` from string-typed fields since these
    constraints are only valid for numeric types in JSON Schema.
    """
    if not isinstance(obj, dict):
        return

    # Fix invalid discriminator definitions
    fix_invalid_discriminator(obj)

    # Check if this is a schema object with type: string
    if obj.get("type") == "string":
        # Remove minimum if present and not numeric (shouldn't be there at all for strings)
        if "minimum" in obj and not is_numeric(obj["minimum"]):
            del obj["minimum"]
        # Remove maximum if present and not numeric
        if "maximum" in obj and not is_numeric(obj["maximum"]):
            del obj["maximum"]
        # Remove invalid default values
        if "default" in obj:
            default_val = obj["default"]
            # Remove "now + X days" style defaults from date-time fields
            if (
                obj.get("format") == "date-time"
                and isinstance(default_val, str)
                and "now" in default_val.lower()
            ):
                del obj["default"]
            # Remove duration string defaults (e.g., "P7D", "P0D") - they can't be parsed as timedelta
            elif obj.get("format") == "duration" and isinstance(default_val, str):
                del obj["default"]

    # Recurse into nested structures
    for value in obj.values():
        if isinstance(value, dict):
            fix_schema_object(value)
        elif isinstance(value, list):
            for item in value:
                fix_schema_object(item)

fix_string_values

fix_string_values(obj)

Recursively fix string values in the spec.

Converts ::: admonition syntax to MkDocs Material !!! syntax in description fields.

Source code in shared/services/payment_providers/revolut/openapi/fix_openapi_spec.py
def fix_string_values(obj: Any) -> None:
    """
    Recursively fix string values in the spec.

    Converts ::: admonition syntax to MkDocs Material !!! syntax in description fields.
    """
    if isinstance(obj, dict):
        for key, value in obj.items():
            if key == "description" and isinstance(value, str):
                obj[key] = convert_admonitions(value)
            else:
                fix_string_values(value)
    elif isinstance(obj, list):
        for item in obj:
            fix_string_values(item)

is_numeric

is_numeric(value)

Check if a value is numeric (int or float).

Source code in shared/services/payment_providers/revolut/openapi/fix_openapi_spec.py
def is_numeric(value: Any) -> bool:
    """Check if a value is numeric (int or float)."""
    return isinstance(value, (int, float)) and not isinstance(value, bool)

main

main()
Source code in shared/services/payment_providers/revolut/openapi/fix_openapi_spec.py
def main() -> None:
    if len(sys.argv) != 2:
        sys.stderr.write(f"Usage: {sys.argv[0]} <input.yaml>\n")
        sys.exit(1)

    input_file = sys.argv[1]

    with open(input_file, encoding="utf-8") as f:
        spec = yaml.safe_load(f)

    fixed_spec = fix_openapi_spec(spec)

    # Output to stdout with proper YAML formatting
    yaml.dump(
        fixed_spec,
        sys.stdout,
        default_flow_style=False,
        allow_unicode=True,
        sort_keys=False,
        width=120,
    )

shared.services.payment_providers.revolut.revolut_client

RevolutAuth

RevolutAuth(token)

Bases: AuthBase

Source code in shared/services/payment_providers/revolut/revolut_client.py
def __init__(self, token: str) -> None:
    self.token = token

__call__

__call__(r)
Source code in shared/services/payment_providers/revolut/revolut_client.py
def __call__(self, r: requests.PreparedRequest) -> requests.PreparedRequest:
    r.headers["Authorization"] = f"Bearer {self.token}"
    return r

token instance-attribute

token = token

RevolutClient

RevolutClient(
    business_account_name, reimbursement_account_id=None
)
Source code in shared/services/payment_providers/revolut/revolut_client.py
def __init__(  # type: ignore[no-untyped-def]
    self,
    business_account_name: RevolutBusinessAccountName,
    reimbursement_account_id=None,
) -> None:
    if business_account_name is None:
        business_account_name = current_config.get("REVOLUT_MAIN_ACCOUNT_NAME")  # type: ignore[unreachable]

    if business_account_name is None:
        raise ValueError("Revolut Business account name is not set configured")

    revolut_config = get_revolut_config_from_business_account_name(
        business_account_name
    )
    private_key = raw_secret_from_config(
        config_key=revolut_config.private_key_secret_name,
        default_secret_value=current_config.get(revolut_config.private_key),
    )
    if private_key is None:
        raise ValueError("Revolut private key is not set/configured")
    self._client_id = get_config_or_fail(revolut_config.client_id)
    self._private_key = private_key
    self._base_url = get_config_or_fail(revolut_config.base_url)

    # Alan Services Revolut config doesn't have default reimbursement account reimbursement_account_id parameter should be passed
    if not revolut_config.reimbursement_account_id:
        if not reimbursement_account_id:
            raise ValueError(
                "Reimbursement account ID is missing to initialize Revolut client"
            )
        self._reimbursement_account_id = reimbursement_account_id
    else:
        self._reimbursement_account_id = (
            reimbursement_account_id
            or get_config_or_fail(revolut_config.reimbursement_account_id)
        )

    self._revolut_issuer = get_config_or_fail(revolut_config.issuer)

    secret_name = get_config_or_fail(revolut_config.api_credentials_secret_name)
    if not secret_name:
        raise RuntimeError(
            f"{revolut_config.api_credentials_secret_name} is not set"
        )

    self._secrets_manager_client = SecretsManager(secret_name)

AUTH_ENDPOINT class-attribute instance-attribute

AUTH_ENDPOINT = 'auth/token'

COUNTERPARTY_ENDPOINT class-attribute instance-attribute

COUNTERPARTY_ENDPOINT = 'counterparty'

PAY_ENDPOINT class-attribute instance-attribute

PAY_ENDPOINT = 'pay'

TRANSACTIONS_ENDPOINT class-attribute instance-attribute

TRANSACTIONS_ENDPOINT = 'transactions'

TRANSACTION_ENDPOINT class-attribute instance-attribute

TRANSACTION_ENDPOINT = 'transaction/{}'

add_counterparty

add_counterparty(
    *,
    company_name=None,
    first_name=None,
    last_name=None,
    iban,
    bic,
    bank_country=None,
    address
)

Create a new counterparty in Revolut https://developer.revolut.com/docs/business/add-counterparty ⧉

:param bank_country: Set by default to the IBAN first two characters. Must be overridden for countries like French Polynesia (=> PF). You can use IBANClient().get_iban_info(iban) to fetch it

Source code in shared/services/payment_providers/revolut/revolut_client.py
def add_counterparty(
    self,
    *,
    # This field must be specified if the counterparty is a company or a hospital
    company_name: str | None = None,
    # These two fields must be specified if the counterparty is an individual
    first_name: str | None = None,
    last_name: str | None = None,
    iban: str,
    bic: str | None,
    bank_country: str | None = None,
    address: BillingAddress | None,
) -> CounterPartyInfo:
    """
    Create a new counterparty in Revolut https://developer.revolut.com/docs/business/add-counterparty

    :param bank_country: Set by default to the IBAN first two characters. Must be overridden for countries like French Polynesia (=> PF).
        You can use IBANClient().get_iban_info(iban) to fetch it
    """
    current_logger.info("Creating Revolut Counterparty...")

    bank_country = bank_country or iban[:2]
    # Revolut counterparty API will fail if company name contains more than 80 characters
    normalized_company_name = self._normalize_name_for_revolut(
        company_name, max_length=80
    )
    # Revolut counterparty API will fail if user names contains more than 40 characters
    normalized_first_name = self._normalize_name_for_revolut(
        first_name, max_length=40
    )
    normalized_last_name = self._normalize_name_for_revolut(
        last_name, max_length=40
    )

    payload = {
        "bank_country": bank_country,
        "currency": self.get_supported_currency(),
    }

    # Revolut does not handle properly new IBAN format from Senegal (at least), so they shared a
    # workaround to avoid rejections: https://alanhealth.slack.com/archives/CPN8N22LU/p1723818151846399
    # For French polynesia we need to send the account_no and bic without the IBAN
    # https://alanhealth.slack.com/archives/CPN8N22LU/p1709893605379069?thread_ts=1709813063.688019&cid=CPN8N22LU
    if bank_country in {"SN", "PF", "NC"}:
        payload["account_no"] = iban
        payload["bic"] = mandatory(
            bic, f"Swift is mandatory for IBAN from {bank_country}"
        )
    else:
        payload["iban"] = iban
        if bic:
            # swift/bic is optional for some countries like SEPA countries
            payload["bic"] = bic

    if normalized_company_name:
        payload["company_name"] = normalized_company_name
    elif normalized_first_name or normalized_last_name:
        payload["individual_name"] = {  # type: ignore[assignment]
            "first_name": normalized_first_name,
            "last_name": normalized_last_name,
        }
    else:
        current_logger.warning(
            "Trying to create a counterparty with no name. This should not happen and might get flagged by Revolut's compliance team.",
            stack_info=True,
        )

    if address:
        # We don't have validation on address.country. UK is not a valid country code, it should be GB.
        # This breaks payments to Revolut, so correcting here.
        country = "GB" if address.country == "UK" else address.country

        payload["address"] = {  # type: ignore[assignment]
            "street_line1": address.street,
            "postcode": address.postal_code,
            "city": address.city,
            "country": country,
        }

    try:
        counterparty_info: CounterPartyInfo | None = self._do_add_counterparty(
            payload
        )
    except PotentialSwiftError as e:
        counterparty_info = self._retry_add_counterparty_without_bic(
            payload=payload
        )
        if not counterparty_info:
            raise e

    current_logger.info(f"Created Revolut Counterparty {counterparty_info}.")
    return mandatory(counterparty_info)

create_payment

create_payment(
    *,
    request_id,
    amount_in_cents,
    receiver_counterparty_id,
    receiver_account_id,
    payment_description=None
)

https://developer.revolut.com/docs/business/create-payment ⧉

Trigger a payment using Revolut.

The payment will be sent to the recipient described by revolut_counterparty_id.

Source code in shared/services/payment_providers/revolut/revolut_client.py
def create_payment(
    self,
    *,
    # The request_id is controlled by us, and is a string of 40 chars max
    request_id: str,
    amount_in_cents: int,
    receiver_counterparty_id: str,
    receiver_account_id: str,
    payment_description: str | None = None,
) -> PaymentInfo:
    """
    https://developer.revolut.com/docs/business/create-payment

    Trigger a payment using Revolut.

    The payment will be sent to the recipient described by revolut_counterparty_id.
    """

    if not request_id:
        raise ValueError("request_id is not set")

    payment_currency = self.get_supported_currency()
    payload = {
        "request_id": request_id,
        "account_id": str(self._reimbursement_account_id),
        "receiver": {
            "counterparty_id": receiver_counterparty_id,
            "account_id": receiver_account_id,
        },
        "amount": amount_in_cents / 100,
        "currency": payment_currency,
    }
    if payment_description:
        payload["reference"] = payment_description

    request = requests.Request(
        method="POST",
        url=f"{self._base_url}{self.PAY_ENDPOINT}",
        json=payload,
    ).prepare()

    # Revolut forbids issuing concurrent payment requests
    with AdvisoryLock("revolut_create_payment"):
        response = self._authenticate_and_execute_request(
            request=request,
            endpoint=self.PAY_ENDPOINT,
            log_params={
                "request_id": request_id,
                "counterparty_id": receiver_counterparty_id,
                "counterparty_account_id": receiver_account_id,
            },
        )

    transaction_id = response["id"]
    created_at = isoparse(response["created_at"])
    transfer_type = self._parse_transfer_type(
        transaction_id=transaction_id,
        transfer_type=response.get("expected_processing_time"),
    )

    return PaymentInfo(
        counterparty_id=receiver_counterparty_id,
        account_id=receiver_account_id,
        transaction_id=transaction_id,
        request_id=request_id,
        created_at=created_at,
        transfer_type=transfer_type,
        currency=payment_currency,
        payment_payload=payload,
    )

get_refund_transactions

get_refund_transactions(since, until=None, count=1000)
Source code in shared/services/payment_providers/revolut/revolut_client.py
def get_refund_transactions(
    self,
    since: date,
    until: date | None = None,  # included
    count: int = 1000,
) -> Iterable[RevolutTransactionInfo]:
    return self.get_transactions(
        transaction_type=RevolutTransactionType.refund,
        since=since,
        until=until,
        count=count,
        account_id=self._reimbursement_account_id,
    )

get_supported_currency

get_supported_currency()
Source code in shared/services/payment_providers/revolut/revolut_client.py
def get_supported_currency(self) -> str:
    return current_config.get("REVOLUT_CURRENCY", "EUR")  # type: ignore[no-any-return]

get_transaction

get_transaction(
    *,
    transaction_id=None,
    request_id=None,
    ignore_not_found_error=False
)

https://developer.revolut.com/docs/business/get-transaction ⧉

Source code in shared/services/payment_providers/revolut/revolut_client.py
@retry(
    stop=stop_after_attempt(4),
    retry=retry_if_exception_type(RevolutInternalError),
    wait=wait_fixed(timedelta(milliseconds=5000)),
    reraise=True,
    before_sleep=before_sleep_log(current_logger, logging.INFO),  # type: ignore[arg-type]
)
def get_transaction(
    self,
    *,
    transaction_id: str | None = None,
    request_id: str | None = None,
    ignore_not_found_error: bool = False,
) -> RevolutTransactionInfo | None:
    """
    https://developer.revolut.com/docs/business/get-transaction
    """

    if transaction_id is None and request_id is None:
        raise ValueError("Either transaction_id or request_id must be provided")
    if transaction_id and request_id:
        raise ValueError(
            "Only one of transaction_id or request_id must be provided"
        )

    request = requests.Request(
        method="GET",
        url=f"{self._base_url}{self.TRANSACTION_ENDPOINT.format(transaction_id or request_id)}",
        params={"id_type": "request_id"} if request_id else None,
        headers={"Cache-Control": "no-cache"},
    ).prepare()

    try:
        info = self._authenticate_and_execute_request(
            request=request,
            endpoint=self.TRANSACTION_ENDPOINT,
            log_params={
                "transaction_id": transaction_id,
                "request_id": request_id,
            },
        )

        return RevolutTransactionInfo(
            id=info["id"],
            type=RevolutTransactionType(info["type"]),
            request_id=info["request_id"],
            state=RevolutTransactionState(info["state"]),
            created_at=isoparse(info["created_at"]),
            updated_at=isoparse(info["updated_at"]),
            completed_at=(
                isoparse(info["completed_at"]) if info.get("completed_at") else None
            ),
            reason_code=info.get("reason_code"),
            related_transaction_id=info.get("related_transaction_id"),
            reference=info.get("reference"),
            legs=[
                RevolutTransactionInfo.Leg(
                    leg_id=leg["leg_id"],
                    amount=leg["amount"],
                    currency=leg["currency"],
                    bill_amount=leg.get("bill_amount"),
                    bill_currency=leg.get("bill_currency"),
                    account_id=leg["account_id"],
                )
                for leg in info["legs"]
            ],
        )
    except RevolutError as e:
        if ignore_not_found_error and isinstance(e, RevolutNotFoundError):
            return None
        raise e

get_transactions

get_transactions(
    transaction_type,
    since,
    until=None,
    count=1000,
    account_id=None,
)

https://developer.revolut.com/docs/business/get-transactions ⧉

Source code in shared/services/payment_providers/revolut/revolut_client.py
def get_transactions(
    self,
    transaction_type: RevolutTransactionType,  # `type` is reserved
    since: date,
    until: date | None = None,  # included
    count: int = 1000,
    account_id: str | None = None,
) -> Iterable[RevolutTransactionInfo]:
    """
    https://developer.revolut.com/docs/business/get-transactions
    """
    if count > 1000:
        # https://developer.revolut.com/docs/api-reference/business/#operation/getTransactions
        raise ValueError(
            "Revolut doesn't support returning more than 1000 transactions"
        )

    if until is None:
        until = date.today()

    for start_date, end_date in months_in_interval(
        start_date=since,
        end_date=until,
    ):
        params = {
            "type": str(transaction_type),
            "count": count,
            "from": start_date.isoformat(),
            "to": end_date.isoformat(),
            "account": account_id,
        }

        request = requests.Request(
            method="GET",
            url=f"{self._base_url}{self.TRANSACTIONS_ENDPOINT}",
            params=params,
            headers={"Cache-Control": "no-cache"},
        ).prepare()

        transactions = self._authenticate_and_execute_request(
            request=request,
            endpoint=self.TRANSACTIONS_ENDPOINT,
        )

        current_logger.debug(
            "Call to Revolut API returned %d transactions", len(transactions)
        )
        for info in transactions:
            yield RevolutTransactionInfo(
                id=info["id"],
                type=RevolutTransactionType(info["type"]),
                request_id=info["request_id"],
                state=RevolutTransactionState(info["state"]),
                created_at=isoparse(info["created_at"]),
                updated_at=isoparse(info["updated_at"]),
                completed_at=(
                    isoparse(info["completed_at"])
                    if info.get("completed_at")
                    else None
                ),
                reason_code=info.get("reason_code"),
                related_transaction_id=info.get("related_transaction_id"),
                reference=info.get("reference"),
                legs=[
                    RevolutTransactionInfo.Leg(
                        leg_id=leg["leg_id"],
                        amount=leg["amount"],
                        currency=leg["currency"],
                        bill_amount=leg.get("bill_amount"),
                        bill_currency=leg.get("bill_currency"),
                        account_id=leg["account_id"],
                    )
                    for leg in info["legs"]
                ],
            )

rotate_access_token

rotate_access_token()
Source code in shared/services/payment_providers/revolut/revolut_client.py
def rotate_access_token(self) -> None:
    data = {
        "grant_type": "refresh_token",
        "refresh_token": self._secrets_manager_client.get("refresh_token"),
        "client_id": self._client_id,
        "client_assertion_type": "urn:ietf:params:oauth:client-assertion-type:jwt-bearer",
        "client_assertion": jwt.encode(
            {
                "iss": self._revolut_issuer,
                "sub": self._client_id,
                "aud": "https://revolut.com",
                "exp": datetime.utcnow() + timedelta(seconds=30),
            },
            self._private_key,
            algorithm="RS256",
            headers={"alg": "RS256", "typ": "JWT"},
        ),
    }

    request = requests.Request(
        method="POST",
        url=self._base_url + self.AUTH_ENDPOINT,
        data=data,
        headers={"Content-Type": "application/x-www-form-urlencoded"},
    ).prepare()

    current_logger.debug("Calling Revolut API: POST %s", request.url)

    # Note: request does not need to be authenticated
    response = requests.Session().send(request)
    self._raise_for_status(response, endpoint=self.AUTH_ENDPOINT)

    self._secrets_manager_client.set(
        "access_token", response.json()["access_token"]
    )

shared.services.payment_providers.revolut.revolut_webhooks

Decorator for validating Revolut webhook signatures.

Revolut webhooks are signed using HMAC-SHA256. The signature is computed as: 1. Concatenate: version.timestamp.raw_payload 2. Compute HMAC-SHA256 of this string using the signing secret 3. Compare with signature from Revolut-Signature header

See: https://developer.revolut.com/docs/guides/manage-accounts/webhooks/verify-the-payload-signature ⧉

RevolutWebhooks

Decorator for incoming Revolut webhooks.

Validates webhook signatures to ensure requests are authentic.

Usage:

@app.route("/my_revolut_webhook", methods=["POST"])
@RevolutWebhooks.webhook_handler()
def revolut_webhook_controller():
    ...

Tags

is_signature_valid staticmethod

is_signature_valid(signing_secret)

Validate Revolut webhook signature using HMAC-SHA256.

The signature is computed as follows: 1. payload_to_sign = v1.{timestamp}.{raw_payload} 2. hmac_signature = HMAC-SHA256(signing_secret, payload_to_sign) 3. expected_signature = v1={hmac_signature}

Parameters:

Name Type Description Default
signing_secret str

The webhook signing secret from Revolut

required

Returns:

Type Description
bool

True if signature is valid, False otherwise

Source code in shared/services/payment_providers/revolut/revolut_webhooks.py
@staticmethod
def is_signature_valid(signing_secret: str) -> bool:
    """
    Validate Revolut webhook signature using HMAC-SHA256.

    The signature is computed as follows:
    1. payload_to_sign = v1.{timestamp}.{raw_payload}
    2. hmac_signature = HMAC-SHA256(signing_secret, payload_to_sign)
    3. expected_signature = v1={hmac_signature}

    Args:
        signing_secret: The webhook signing secret from Revolut

    Returns:
        True if signature is valid, False otherwise
    """
    signature_header = request.headers.get("Revolut-Signature")
    timestamp_header = request.headers.get("Revolut-Request-Timestamp")

    if not signature_header or not timestamp_header:
        current_logger.warning(
            "Missing signature or timestamp header",
            has_signature=bool(signature_header),
            has_timestamp=bool(timestamp_header),
        )
        return False

    raw_payload = request.get_data(as_text=True)

    # Parse JSON and re-serialize with compact format (no spaces)
    import json

    payload_dict = json.loads(raw_payload)
    compact_payload = json.dumps(payload_dict, separators=(",", ":"))

    version = "v1"
    payload_to_sign = f"{version}.{timestamp_header}.{compact_payload}"

    # Compute HMAC-SHA256
    computed_hmac = hmac.new(
        signing_secret.encode("utf-8"),
        payload_to_sign.encode("utf-8"),
        sha256,
    ).hexdigest()

    # Construct expected signature: v1=hmac_value
    expected_signature = f"{version}={computed_hmac}"

    # Extract signature(s) from header
    # Header format: "v1=signature" or "v1=sig1,v1=sig2" (multiple signatures)
    signatures = [sig.strip() for sig in signature_header.split(",")]

    # Check if expected signature matches any of the provided signatures
    for sig in signatures:
        if hmac.compare_digest(sig, expected_signature):
            return True

    current_logger.warning(
        "Signature mismatch",
        expected_signature=expected_signature,
        received_signatures=signatures,
        payload_to_sign=payload_to_sign,
    )
    return False

webhook_handler staticmethod

webhook_handler()

Handle Revolut webhook requests and validate their signature.

The business account name is extracted from the query parameter to fetch the appropriate signing secret for that account.

Returns:

Type Description
callable

Decorated function that validates webhook signatures

Source code in shared/services/payment_providers/revolut/revolut_webhooks.py
@staticmethod
def webhook_handler() -> callable:  # type: ignore[valid-type]
    """
    Handle Revolut webhook requests and validate their signature.

    The business account name is extracted from the query parameter to fetch
    the appropriate signing secret for that account.

    Returns:
        Decorated function that validates webhook signatures
    """

    def decorator(handler: callable) -> callable:  # type: ignore[valid-type]
        @wraps(handler)
        def decorated_function(*args, **kwargs) -> Response:  # type: ignore[no-untyped-def]
            business_account_name = request.args.get("business_account_name")

            if not business_account_name:
                current_logger.error(
                    "Missing business_account_name query parameter for signature verification"
                )
                return make_response("Missing business account name", 400)

            # Get signing secret for this specific business account
            signing_secret = _get_signing_secret_for_account(business_account_name)

            if not signing_secret:
                current_logger.error(
                    "Revolut signing secret not configured",
                    business_account_name=business_account_name,
                )
                return make_response("Configuration error", 500)

            if not RevolutWebhooks.is_signature_valid(signing_secret):
                current_logger.warning(
                    "Invalid Revolut webhook signature",
                    business_account_name=business_account_name,
                    headers=dict(request.headers),
                )
                return make_response("Invalid signature", 401)

            try:
                response = handler(*args, **kwargs)  # type: ignore[misc]
            except Exception as e:
                current_logger.error(
                    "Revolut webhook handler failed",
                    error=e,
                    error_message=str(e),
                    business_account_name=business_account_name,
                )
                response = make_response("Internal error", 500)

            return response  # type: ignore[no-any-return]

        return decorated_function

    return decorator