Search by

znojil / revolut-business

znojil

💸 A simple and modern PHP library for communicating with the Revolut API.

Package info

github.com/znojil/revolut-business

pkg:composer/znojil/revolut-business

Statistics

Installs: 2

Dependents: 0

Suggesters: 0

Stars: 0

Open Issues: 0

v1.1.0 2026-09-05 14:01 UTC

This package is auto-updated.

Last update: 2026-09-05 14:07:29 UTC


README

Latest Stable Version PHP Version Require License Tests

A simple and modern PHP library for communicating with the Revolut Business API.

Covers all 98 endpoints of the Merchant API v1.0 and the Webhooks API v2.0, with typed DTOs, enums and full PHPStan level max coverage.

🚀 Installation

composer require znojil/revolut-business

🔑 Authorization

The Revolut Business API uses OAuth 2.0 with a JWT client assertion. Before you can send a single request, you need to complete a one-time consent flow:

  1. Create an API certificate. In Revolut Business, go to Settings → API, upload your public key and set a redirect URI. Revolut gives you a client ID and you choose an issuer (the domain of your redirect URI).
  2. Get the authorization code. Open the consent link shown next to your certificate. After you confirm, Revolut redirects to your redirect URI with a ?code= query parameter.
  3. Exchange the code for a token pair — once, using Client::authorize().

See Make your first API request for the full walkthrough.

use Znojil\RevolutBusiness\Client;
use Znojil\RevolutBusiness\Config;
use Znojil\RevolutBusiness\FileTokenStorage;

$config = new Config(
	clientId: 'YOUR_CLIENT_ID',
	issuer: 'example.com', // the domain of your redirect URI
	privateKey: file_get_contents('/path/to/privatekey.pem'),
	sandbox: false // true for the sandbox environment
);

$client = new Client($config, new FileTokenStorage('/path/to/tokens.json'));

// once, with the code from the redirect
$client->authorize($_GET['code']);

From then on the library handles tokens on its own — the access token is refreshed from the stored refresh token whenever it expires, so you only ever call send().

The refresh token expires after 90 days and cannot be renewed automatically. Once that happens, AuthenticationException is thrown and you have to run the consent flow again.

📖 Usage

1. Sending a Request

Every endpoint is a *Request class. Pass it to Client::send() and you get back a typed result:

use Znojil\RevolutBusiness\Request\GetAccountsRequest;

foreach($client->send(new GetAccountsRequest) as $account){
	$account->id; // string
	$account->name; // ?string
	$account->balance; // float
	$account->currency; // Currency enum, or string for a currency not in the enum
	$account->state; // AccountState enum
	$account->accountType; // AccountType enum
	$account->createdAt; // DateTimeImmutable
}

Requests that need a body take their required parameters first and the optional ones as named arguments:

use Znojil\RevolutBusiness\DTO\PaymentReceiverDTO;
use Znojil\RevolutBusiness\Enum\Currency;
use Znojil\RevolutBusiness\Enum\TransferReasonCode;
use Znojil\RevolutBusiness\Request\CreatePaymentRequest;

$result = $client->send(new CreatePaymentRequest(
	accountId: '05018b0d-e67c-4fec-bea6-415e9da9432c',
	receiver: new PaymentReceiverDTO('7e18625a-3e6c-4d4f-8429-216c25309a5f', null, null),
	amount: 123.45,
	currency: Currency::Gbp,
	requestId: 'invoice-2026-03', // your own idempotency key
	reference: 'Invoice 2026/03',
	transferReasonCode: TransferReasonCode::Services
));

$result->id; // string
$result->state; // TransactionState enum

Listing endpoints are paginated by time, not by page number — you walk backwards using the timestamp of the oldest item you received:

use Znojil\RevolutBusiness\Request\GetTransactionsRequest;

$before = null;
do{
	$transactions = $client->send(new GetTransactionsRequest(to: $before, count: 100));

	foreach($transactions as $transaction){
		$transaction->id;
		$transaction->type; // TransactionType enum
		$transaction->createdAt;
	}

	$before = $transactions !== [] ? end($transactions)->createdAt : null;
}while(count($transactions) === 100);

2. Custom Token Storage

FileTokenStorage stores the token pair as JSON in a file with 0600 permissions. To keep tokens in a database or a cache instead, implement Znojil\RevolutBusiness\TokenStorage:

use Znojil\RevolutBusiness\TokenPair;
use Znojil\RevolutBusiness\TokenStorage;

final class MyTokenStorage implements TokenStorage{

	public function load(): ?TokenPair{
		// return null when nothing is stored yet
	}

	public function save(TokenPair $tokenPair): void{
		$tokenPair->accessToken; // string
		$tokenPair->expirationDatetime; // DateTimeImmutable
		$tokenPair->refreshToken; // string
	}

}

$client = new Client($config, new MyTokenStorage);

save() is called both by authorize() and by every automatic token refresh, so whatever you write to must be durable — losing the refresh token means going through the consent flow again.

3. Using a Custom HTTP Client

By default the library uses znojil/http. You can inject your own implementation as the third argument to the Client constructor. It must implement the Znojil\RevolutBusiness\Http\Client interface.

use Znojil\RevolutBusiness\Client;
use Znojil\RevolutBusiness\Http\Client as RevolutHttpClient;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\UriInterface;

class MyCustomHttpClient implements RevolutHttpClient{
	public function send(string $method, string|UriInterface $uri, array $headers = [], mixed $data = null, array $options = []): ResponseInterface{
		// your implementation
	}
}

$client = new Client($config, $tokenStorage, new MyCustomHttpClient);

String keys in $options are Znojil\RevolutBusiness\Http\Option::* enum values that every implementation must honor, and unknown string keys must be rejected with an exception. Integer keys are raw CURLOPT_* constants — non-cURL implementations must reject them with an exception rather than silently ignore them, so that a consumer never ends up with options that silently don't apply.

🧩 Conventions

A few things that are specific to this library rather than to the API itself.

Clearing a Property

On Update* requests, null and "remove this value" are two different intentions, so null cannot mean both. Passing null leaves a property unchanged; to have the API remove it, pass the Clear::Value sentinel:

use Znojil\RevolutBusiness\Clear;
use Znojil\RevolutBusiness\Request\UpdatePaymentDraftRequest;

// only the title is sent, the schedule stays as it is
$client->send(new UpdatePaymentDraftRequest($draftId, title: 'New title'));

// the schedule is explicitly removed
$client->send(new UpdatePaymentDraftRequest($draftId, scheduleFor: Clear::Value));

Where a property cannot be cleared through the API, its parameter simply doesn't accept Clear.

Requests That Need At Least One Property

Most Update* endpoints reject an empty body. Those requests throw InvalidArgumentException when you construct one with nothing to update, rather than sending a request that is guaranteed to fail.

Enums

Most enums are closed: an unknown value in a response throws UnexpectedValueException, so you learn about an API change instead of silently getting a wrong value.

Three of them are open, because their real value sets are larger than what the documentation lists and a new entry must not break a running application. Currency, PaymentRoute and TransferReasonCode are typed as string|Enum on the response side — you get the enum case when the value is known and the raw string when it isn't:

use Znojil\RevolutBusiness\Enum\Currency;
use Znojil\RevolutBusiness\Request\GetAccountRequest;

$account = $client->send(new GetAccountRequest($accountId));

if($account->currency instanceof Currency){
	// a known currency
}else{
	// a currency not covered by the enum, as a raw string
}

On the request side these are always plain enums — you can only send what the library knows.

Parameter Validation

Requests do not validate their parameters. The API returns precise errors for invalid input, and duplicating those rules here would mean maintaining a second copy of somebody else's specification. Every request class links to its official documentation page in a @link annotation.

📋 Available Requests

All 98 requests live in the Znojil\RevolutBusiness\Request namespace, grouped here the same way as the official documentation.

Accounting

Request Documentation
CreateAccountingCategoryRequest Create an accounting category
GetAccountingCategoriesRequest Retrieve accounting categories
GetAccountingCategoryRequest Retrieve an accounting category
UpdateAccountingCategoryRequest Update an accounting category
DeleteAccountingCategoryRequest Delete an accounting category
CreateLabelGroupRequest Create a label group
GetLabelGroupsRequest Retrieve label groups
GetLabelGroupRequest Retrieve a label group
UpdateLabelGroupRequest Update a label group
DeleteLabelGroupRequest Delete a label group
CreateLabelRequest Create a label
GetLabelsRequest Retrieve labels
UpdateLabelRequest Update a label
DeleteLabelRequest Delete a label
CreateTaxRateRequest Create a tax rate
GetTaxRatesRequest Retrieve tax rates
GetTaxRateRequest Retrieve a tax rate
UpdateTaxRateRequest Update a tax rate
DeleteTaxRateRequest Delete a tax rate

Accounts

Request Documentation
GetAccountsRequest Retrieve all accounts
GetAccountRequest Retrieve an account
GetAccountBankDetailsRequest Retrieve account's full bank details

Cards

Request Documentation
GetCardsRequest Retrieve all cards
CreateCardRequest Create a card
GetCardRequest Retrieve a card
UpdateCardRequest Update a card
TerminateCardRequest Terminate a card
UpdateCardContactsRequest Update card contacts
UpdateCardReferencesRequest Update card references
FreezeCardRequest Freeze a card
UnfreezeCardRequest Unfreeze a card
LockCardRequest Lock a card
UnlockCardRequest Unlock a card
GetSensitiveCardDetailsRequest Retrieve sensitive card details

Card invitations

Request Documentation
CreateCardInvitationRequest Create a card invitation
GetCardInvitationsRequest Retrieve card invitations
GetCardInvitationRequest Retrieve a card invitation
UpdateCardInvitationRequest Update a card invitation
CancelCardInvitationRequest Cancel a card invitation

Counterparties

Request Documentation
ValidateAccountNameRequest Validate an account name
GetCounterpartiesRequest Retrieve counterparties
CreateCounterpartyRequest Create a counterparty
GetCounterpartyCountriesRequest Retrieve counterparty countries
GetCounterpartyFieldsRequest Retrieve counterparty requirements
GetCounterpartyRequest Retrieve a counterparty
DeleteCounterpartyRequest Delete a counterparty
UpdateCounterpartyPaymentMethodRequest Update a counterparty payment method

Expenses

Request Documentation
GetExpensesRequest Retrieve expenses
GetExpenseRequest Retrieve an expense
GetExpenseReceiptRequest Retrieve an expense receipt

Foreign exchange

Request Documentation
GetExchangeRateRequest Get exchange rate
ExchangeMoneyRequest Exchange money
GetExchangeReasonsRequest Retrieve exchange reasons

Payment drafts

Request Documentation
GetPaymentDraftsRequest Retrieve payment drafts
CreatePaymentDraftRequest Create a payment draft
GetPaymentDraftRequest Retrieve a payment draft
DeletePaymentDraftRequest Delete a payment draft
UpdatePaymentDraftRequest Update a payment draft
CreatePaymentDraftPaymentRequest Add a payment to a draft
UpdatePaymentDraftPaymentRequest Update a draft payment
DeletePaymentDraftPaymentRequest Delete a draft payment

Payout links

Request Documentation
CreatePayoutLinkRequest Create a payout link
GetPayoutLinksRequest Retrieve payout links
GetPayoutLinkRequest Retrieve a payout link
CancelPayoutLinkRequest Cancel a payout link

Simulations

Sandbox only.

Request Documentation
SimulateTransactionStateRequest Simulate a transfer state update
SimulateTopUpRequest Simulate an account top-up

Teams

Request Documentation
GetTeamMembersRequest Retrieve team members
InviteTeamMemberRequest Invite a team member
GetTeamMemberRequest Retrieve a team member
DeleteTeamMemberRequest Delete a team member
AssignTeamMemberDepartmentRequest Assign a department
UnassignTeamMemberDepartmentRequest Unassign a department
AssignTeamMemberManagerRequest Assign a manager
UnassignTeamMemberManagerRequest Unassign a manager
UpdateTeamMemberRoleRequest Update a team member's role
SuspendTeamMemberRequest Suspend a team member
UnsuspendTeamMemberRequest Unsuspend a team member
GetRolesRequest Retrieve roles
CreateDepartmentRequest Create a department
GetDepartmentsRequest Retrieve departments
GetDepartmentRequest Retrieve a department
UpdateDepartmentRequest Update a department
DeleteDepartmentRequest Delete a department

Transactions

Request Documentation
GetTransactionsRequest Retrieve transactions
GetTransactionRequest Retrieve a transaction

Transfers

Request Documentation
CreatePaymentRequest Create a payment
GetIndicativeQuoteRequest Get an indicative quote
GetPaymentFieldsRequest Retrieve payment requirements
CreateTransferRequest Move money between your accounts
GetTransferReasonsRequest Retrieve transfer reasons

Webhooks

Version 2.0 of the Webhooks API. The deprecated v1 endpoints are not implemented.

Request Documentation
CreateWebhookRequest Create a webhook
GetWebhooksRequest Retrieve webhooks
GetWebhookRequest Retrieve a webhook
UpdateWebhookRequest Update a webhook
DeleteWebhookRequest Delete a webhook
RotateWebhookSigningSecretRequest Rotate a webhook signing secret
GetFailedWebhookEventsRequest Retrieve failed webhook events

⚠️ Error Handling

The client throws exceptions to help you identify the issue:

  • Znojil\RevolutBusiness\Exception\ClientException: For HTTP client-side errors (4xx).
  • Znojil\RevolutBusiness\Exception\ServerException: For HTTP server-side errors (5xx).
  • Znojil\RevolutBusiness\Exception\ResponseException: For other unsuccessful HTTP responses. The base class of the three above — it carries apiErrorCode, apiErrorId and the raw responseBody.
  • Znojil\RevolutBusiness\Exception\JsonException: When a response body is not valid JSON.
  • Znojil\RevolutBusiness\Exception\JsonResponseException: When a response body is valid JSON but not the object or array the endpoint promises (subtype of ResponseException).
  • Znojil\RevolutBusiness\Exception\UnexpectedValueException: When a response contains an enum value the library does not know.
  • Znojil\RevolutBusiness\Exception\InvalidArgumentException: For invalid input (e.g. an update request with no properties to update).
  • Znojil\RevolutBusiness\Exception\MissingTokenException: When no token pair is stored yet — run the authorization flow first.
  • Znojil\RevolutBusiness\Exception\IOException: When FileTokenStorage cannot read or write the token file.
  • Znojil\RevolutBusiness\Auth\Exception\AuthenticationException: When the token exchange or refresh fails (e.g. an expired or revoked refresh token).
  • Znojil\RevolutBusiness\Auth\Exception\ClientAssertionException: When the JWT client assertion cannot be created (e.g. an invalid private key).
use Znojil\RevolutBusiness\Auth\Exception\AuthenticationException;
use Znojil\RevolutBusiness\Exception\ClientException;
use Znojil\RevolutBusiness\Exception\ServerException;

try{
	$result = $client->send(new CreatePaymentRequest(...));
}catch(ClientException $e){
	echo $e->getMessage(); // error message from Revolut
	echo $e->getCode(); // HTTP status code
	echo $e->apiErrorCode; // ?int error code from Revolut
	echo $e->apiErrorId; // ?string error id, useful when contacting support
}catch(ServerException $e){
	// Revolut server error (5xx)
}catch(AuthenticationException $e){
	// the refresh token has expired or been revoked — run the consent flow again
}

All exceptions thrown by the library implement the Znojil\RevolutBusiness\Exception\Exception marker interface, so a single catch can cover them all.

📄 License

This library is open-source software licensed under the MIT license.