# Welcome to the bunq API documentation

{% hint style="danger" %}
By integrating with bunq's Public API, you agree to comply with bunq’s [**Terms & Conditions**](https://static.bunq.com/website/documents/bunq-terms-conditions-api-en.pdf) and all applicable usage policies. Please **review them carefully** before getting started.
{% endhint %}

Hi there! Thanks for stopping by. The bunq API is a powerful way to automate your banking and build custom experiences around your finances—with over 300 available operations to play with.

We get it—because it’s so flexible, the API can feel a bit overwhelming at first. That’s why this guide is here: to help you understand how bunq works behind the scenes, so you can quickly find the right endpoints and build your integration with confidence.

Let’s start by exploring the key building blocks of the bunq API and how they relate to each other.

### Jump right in

<table data-view="cards"><thead><tr><th></th><th></th><th data-hidden data-card-cover data-type="files"></th><th data-hidden></th><th data-hidden data-card-target data-type="content-ref"></th></tr></thead><tbody><tr><td><strong>Tools</strong></td><td>These are the main tools we offer to help you get started right away!</td><td><a href="/files/U0jzhcX3zPWKuMSmiR5I">/files/U0jzhcX3zPWKuMSmiR5I</a></td><td></td><td><a href="/pages/y6OIKVBZhTcWPsB2FTsS">/pages/y6OIKVBZhTcWPsB2FTsS</a></td></tr><tr><td><strong>Basics</strong></td><td>Everything you need to start your integration</td><td><a href="/files/vkg6fjuNGkVQxGCPCo9v">/files/vkg6fjuNGkVQxGCPCo9v</a></td><td></td><td><a href="/pages/cz8JK0p5smEX4RiQu9YT">/pages/cz8JK0p5smEX4RiQu9YT</a></td></tr><tr><td><strong>Tutorials</strong></td><td>Want a step-by-step of our implementations? Check it here!</td><td><a href="/files/9HjzLuugMvSnxeEfCQXw">/files/9HjzLuugMvSnxeEfCQXw</a></td><td></td><td><a href="/pages/MeUxFi3dli4GFhxZLhzr">/pages/MeUxFi3dli4GFhxZLhzr</a></td></tr><tr><td><strong>Third Party Providers</strong></td><td>Are you an Open Banking PSD2 certified partner trying to connect to bunq? Check how to do it here!</td><td><a href="/files/VI0izbEYUxzGiZ0ot0mt">/files/VI0izbEYUxzGiZ0ot0mt</a></td><td></td><td><a href="/pages/qCjntXVg32gpT3POgdl3">/pages/qCjntXVg32gpT3POgdl3</a></td></tr><tr><td><strong>Need help?</strong></td><td>Check our FAQ and our support channels here!</td><td><a href="/files/LR9P2EWnacxrBtwoORUG">/files/LR9P2EWnacxrBtwoORUG</a></td><td></td><td><a href="/pages/pjUhf617WIJ9mhIYR3fr">/pages/pjUhf617WIJ9mhIYR3fr</a></td></tr></tbody></table>


# Tools

To help you build smooth, reliable integrations with bunq's public API, we offer a set of developer-friendly tools designed to make your life easier — from setup to testing and deployment.

💻 [**SDKs (Software Development Kits)**](broken://pages/ZOBhBQTwIMvx9EpWwDg6)\
Our SDKs give you ready-to-use libraries, code samples, and documentation to interact with the bunq API in languages like Java, Python, and PHP. They handle the heavy lifting, so you don't have to worry about low-level details—just plug in and start building. Whether you're creating web apps, backend services, or mobile integrations, the SDKs help you move faster and avoid common pitfalls.

🔧 [**Postman Collection**](https://github.com/bunq/gitbook-documentation/tree/master/getting-started/tools/postman/README.md)\
Use our official Postman collection to explore the API, test requests, and inspect responses—all in an interactive, user-friendly interface. You can easily set headers, manage authentication, and automate tests. It's a great way to understand how the API behaves before diving into code.

📱 [**Android Emulator**](https://github.com/bunq/gitbook-documentation/tree/master/getting-started/tools/android-emulator/README.md)\
Want to see how the end user interacts with the bunq API without needing a physical device? The Android Emulator lets you test on different screen sizes, OS versions, and configurations. It's ideal for making sure your integration works smoothly across various user scenarios.

🤖 [**MCP**](https://github.com/bunq/gitbook-documentation/tree/master/getting-started/tools/mcp/README.md)\
Query the bunq API documentation directly from your AI assistant or MCP-compatible development tool. Connect to our MCP server at `https://doc.bunq.com/~gitbook/mcp` and get answers grounded in our official docs — without leaving your environment.

Together, these tools give you everything you need to develop, test, and maintain powerful applications on top of the bunq ecosystem—no stress, no guesswork. :rocket:


# Postman

[Postman](https://www.postman.com/) is a powerful tool that lets you interact with the bunq API in a visual, no-code environment. You can send requests, tweak parameters, set headers, manage authentication, and inspect responses—all without writing a single line of code.

It’s perfect for:

* Exploring how endpoints work
* Testing different use cases before coding
* Debugging your integration
* Automating API tests

Find our postman collection here:

{% embed url="<https://github.com/bunq/postman>" %}

With our official Postman collection, you’ll hit the ground running—no setup headaches, just straight into experimenting with the API.

## Video instructions on how to make your first API call with postman

{% embed url="<https://youtu.be/wgURHBZIbiQ>" %}


# Android Emulator

{% hint style="warning" %}
It is **not** possible to create accounts using the regular signup in the app, bunq is not reviewing Sandbox applications. See [Creating a sandbox user and getting an API key](/tutorials/your-first-payment/creating-a-sandbox-user-and-getting-an-api-key) to learn how to get a user you can test with.
{% endhint %}

In case you do not own an Android device on which you can run our Sandbox app for end-to-end testing, you can set up an emulator to run the bunq Sandbox app for Android.

### Things you will need

You can choose to install on a physical or virtual device.

* For a physical device you'll need a modern Android Device
* If you want to virtualize the bunq app on your computer you need to install [Android Studio](https://developer.android.com/studio/index.html).

### Download sandbox app

{% hint style="success" %}
Download the APK file from an Android device to install it on that device
{% endhint %}

<a href="https://appstore.bunq.com/api/android/builds/bunq-android-sandbox-master.apk" class="button primary">Download APK File</a>

### Starting the Android Virtual Device (AVD) Manager

1. Open Android Studio.
2. From the top menu, select “Tools” > "Android" > "AVD Manager".

### Setting up a new virtual device

1. Start the wizard by clicking on "+ Create Virtual Device".
2. Select a device (we recommend: "Pixel 5.0" or "Nexus 6") and press "Next".
3. Select an x86 system image (we recommend: Nougat, API Level 25, Android 7.1.1 with Google APIs) and press "Next". The image needs to have Google Play Services 10.0.1 or higher.
4. In the bottom left corner, select "Show Advanced Settings".
5. Scroll to "Memory and Storage".
6. Change "Internal Storage" to "2048 MB".
7. Change "SD card" to "200 MB".
8. Press "Finish".

### Starting the virtual device

1. On the right side under "Actions", select the green "Play" button.
2. Wait for the device to boot, this may take a few minutes.

### Installing the bunq Sandbox App APK

1. Open the command line.
2. Navigate to your Android SDK platform tools directory (e.g. `cd ~/Library/Android/sdk/platform-tools` on macOS).
3. Make sure that the virtual device is started and has fully booted.
4. Run `./adb install ~/Downloads/bunq-android-sandboxEmulator-public-api.apk`, this may take a few minutes, and should finish with "Success".

### Creating an account and logging in

1. Create a sandbox user through the portal
   1. Create a sandbox account in the [developer portal](https://developer.bunq.com/).
   2. Grab the API key from the portal
2. Create a user through the API
3. With the API key [Start a Session](/tutorials/your-first-payment/creating-the-api-context/start-a-session). This will return a [User](/basics/bunq-api-objects/user) which has an alias attached. The Alias is a fake email address or phone number. All pass codes are either 000000 or 992266

{% hint style="info" %}
You will be asked to verify your phone number when you open the app for the first time. Sandbox does not send actual SMS messages. Enter any valid phone number and use the default verification code `992266`.
{% endhint %}


# Documents MCP

bunq's API documentation is available as an MCP server, letting you query it directly from any MCP-compatible AI assistant or development tool — without leaving your\
environment.

Connect

Point your MCP client at:

<https://doc.bunq.com/\\~gitbook/mcp>

### What it does

Once connected, your AI assistant can answer questions about the bunq API grounded in the official documentation — endpoints, authentication, error codes, and more.

### Compatible clients

Any tool that supports the Model Context Protocol works out of the box, including Claude, Cursor, and other MCP-enabled assistants.


# Sandbox Users and Sandbox Money

For you to start building on top of the bunq API you need a sandbox user and sandbox money. Both are easy to obtain

## Sandbox user

Make a call to the[Sandbox Users](/sandbox-users) endpoint to get a sandbox [User](/basics/bunq-api-objects/user) returned. This user will also contain a API key.

## Sandbox Money

You can get sandbox money by making a [Request Inquiry](/request/request-inquiry)to <sugardaddy@bunq.com>. He will grant you money to your sandbox user for amounts up to €500.

## Full tutorial setting up your user

If you want a bit more context on setting up your user then please refer to our [Your first payment](/tutorials/your-first-payment) where we have a entire chapter on [Setting up a sandbox user](/tutorials/your-first-payment/setting-up-a-sandbox-user) including getting the account funded, but also on how to get a copy of the bunq sandbox app.


# Getting started

## Getting Started

### 1. Create a bunq Account

To use the bunq API in production, you'll need a personal or business bunq account, which you can create directly in the [bunq mobile app](https://www.bunq.com).

But if you're just getting started, **you don’t need a real account yet**.

We provide a **sandbox environment** where you can:

* Generate sandbox users instantly
* Access test accounts with fake money
* Experiment freely without risk

To get started with the sandbox account we recommend you start with our tutorial: [Your first payment](/tutorials/your-first-payment) which guides you through all the steps.

### 2. Choose Between Sandbox and Production

bunq offers two environments for developers:

#### 🧪 Sandbox

* **Free** and open to anyone — no real bunq account required
* Preloaded with **test money** and **mock users**
* Safe to experiment, test features, and simulate payments

Ideal for:

* Trying out the API
* Building and testing your integration
* Running automated tests

#### 🏦 Production

* Connects to **real bunq accounts** and **live money**
* Requires a valid bunq personal or business account
* Used for actual payments, user data, and transactions

Use this when:

* You're ready to go live
* You want to interact with real users and money

Follow these instructions to start [Creating a sandbox user and getting an API key](/tutorials/your-first-payment/creating-a-sandbox-user-and-getting-an-api-key) and here to [Getting sandbox money on the user account](/tutorials/your-first-payment/setting-up-a-sandbox-user/getting-sandbox-money-on-the-user-account).

### 3. Generate an API Key or Register an OAuth Client

If you're building something **just for your own bunq account**, you can generate a personal [API Keys](/basics/authentication/api-keys) directly from the bunq app:

* Grants **full access to your own account**
* Ideal for **internal tools, scripts, or personal automations**
* Simple to set up: no redirects, no user consent flow

If you're building an app or service that needs access to **other users’ bunq accounts**, use [OAuth](/basics/authentication/oauth):

* Users grant permission to your app
* You get access **only to the scopes** they approve
* Required for **multi-user apps, SaaS products, and platforms**

### 4. Set Up Your Development Environment

### 5. Install an SDK

To make integration easier, we offer official SDKs in the most popular languages:

* 🐍 **Python**
* ☕️ **Java**
* 💠 **C#**
* 🐘 **PHP**

These SDKs handle encryption, session creation, and API communication for you — so you can focus on building. Check them out here [Broken mention](broken://pages/ZOBhBQTwIMvx9EpWwDg6)

#### ✨ Why use an SDK?

* Simplifies authentication and session handling
* Automatically manages request signing and response parsing
* Speeds up development with helpful abstractions

### 6. Try a Tutorial

We offer a few Tutorials that help you step-by-step to understand the basics. These tutorials exist:

* [Your first payment](/tutorials/your-first-payment)
* [How to do bulk payments](/tutorials/how-to-do-bulk-payments)
* [Receiving payments on your website using bunq.me](/tutorials/receiving-payments-on-your-website-using-bunq.me)
* [How to manage your cards](/tutorials/how-to-manage-your-cards)

### 7. Explore Callbacks // Webhooks for Real-Time Updates

bunq supports **webhooks** to notify your app in real time when something happens — like receiving a payment or a card transaction. These events are also available via the API, but webhooks let you react instantly without polling. Just register a callback URL, and bunq will send a signed POST request whenever an event occurs.

Learn how to set up these callbacks here [Callbacks (Webhooks)](/basics/callbacks-webhooks)


# Authentication

When integrating with the bunq API, you can authenticate using either an **API key** or **OAuth**, depending on your use case. **API keys** provide a straightforward way to authenticate, making them ideal for server-to-server communication and automated scripts. They offer fast access but come with security risks if exposed, as they grant broad permissions. **OAuth**, on the other hand, is designed for third-party applications that need user-specific access. It allows fine-grained permissions and secure authentication without sharing credentials but requires a more complex authorization flow. Choose the method that best fits your needs:

{% hint style="info" %}
OAuth is the obligatory authentication method for public projects whereas using API keys is allowed as long as only you have access to your application.
{% endhint %}

{% content-ref url="/pages/sY7NIUhQd0YOhIITR7nE" %}
[API Keys](/basics/authentication/api-keys)
{% endcontent-ref %}

{% content-ref url="/pages/nQpuM79hqrXIE9iNgCIm" %}
[OAuth](/basics/authentication/oauth)
{% endcontent-ref %}


# API Keys

Guard your API key carefully, as it provides access to sensitive financial information similar to actual banking details. Make sure not to commit it to your source control. If you end up doing so you can always revoke the key from your bunq app.

API Key Authentication The easiest way to authenticate with the bunq API is by using an API key. This key acts as a secret credential that identifies your application. To use an API key, you must: Obtain an API Key – Generate one from the bunq app or request a sandbox API key via POST /sandbox-user. Or grab one from your bunq app in production.

Register Your Device and starting a session – Before making API calls, your device must be registered with POST /device-server. This binds the API key to your device and its IP address. We dedicated a whole page on registering your installation, your device and starting a session here

Session Management Auto Logout: The auto-logout time set in the bunq app applies to all sessions, including API sessions.

Session Extension: If a request is made within 30 minutes of expiration, the session is automatically extended.

Get your sandbox API Key Our tutorial walks you through setting up API keys step by step, and also shows you how to do the subsequent steps.

You can get your API key for the sandbox through a Curl command. The Production API key can be taken from our App. Curl call to obtain API key To create a user and obtain an API key you can just run 1 command from your terminal: Run this command to obtain a user person. It will return just a API key, but in the background we generated a dummy user for you to play around with in the sandbox. curl --location --request POST '<https://public-api.sandbox.bunq.com/v1/sandbox-user-person>'

Run this command to obtain a business user curl --location --request POST '<https://public-api.sandbox.bunq.com/v1/sandbox-user-company>'

This request will create a new user in the system. The response will give you the API key that belongs to this user. Store it somewhere safe.

{ "Response": \[ { "ApiKey": { "api\_key": "sandbox\_a918ac413524f2bf56ceb740595e01839dd7f0321ca08e4c4ea93349" } } ] }

Getting your production API key Be aware that a production API key can be used to control your bank account and make payments on your behalf.

Go to your bunq app

In the settings screen look for the heading 'Developers'

There you select API keys

and click add a API key


# OAuth

{% hint style="danger" %}
**Important Note**\
If you, as a developer, intend to access data belonging to **other bunq users** (e.g., account information, transaction history, or initiate payments on their behalf), you must ensure that you:

* ✅ **Fully comply with** [**bunq’s Terms and Conditions**](https://static.bunq.com/framer/documents/Terms-Conditions-API-EN-20200805.pdf) for API usage.
* ✅ **Obtain explicit user consent** as required.
* ⚖️ **May be subject to** [**PSD2 regulations**](https://eur-lex.europa.eu/legal-content/EN/LSU/?uri=oj:JOL_2015_337_R_0002), including registration as an AISP (Account Information Service Provider) or PISP (Payment Initiation Service Provider) with your national competent authority.

Misuse or non-compliance may lead to access being revoked or legal consequences.
{% endhint %}

## OAuth Authentication

OAuth 2.0 is an industry-standard protocol that allows third-party applications to securely access bunq user accounts without exposing credentials. This method enables seamless authorization while maintaining user control over permissions.

### **How OAuth Works**

OAuth lets your app request access to a bunq user’s account. If the user grants permission, your app receives an **access token**, which functions similarly to an API key but with predefined scopes. Depending on your use case, you may need a **PSD2 permit** to access sensitive financial data or initiate payments on behalf of users.

### **Getting Started with OAuth**

To integrate OAuth authentication, follow these steps:

1. [Register OAuth Client](/basics/authentication/oauth/register-oauth-client)– Create an app in bunq Developer and add at least one Redirect URL.
2. **Get OAuth Credentials** – Retrieve your `client_id` and `client_secret` from the app settings in bunq Developer.
3. [Redirect Users to Authorize your app](/basics/authentication/oauth/redirect-users-to-authorize-your-app) – Send users to the OAuth authorization URL.
4. [Handle the Authorization Code](/basics/authentication/oauth/handle-the-authorization-code) – If the user grants access, they are redirected to your `redirect_uri` with an authorization code.
5. **Exchange the Code for an Access Token** – Use the token endpoint to retrieve an access token. You should safely store these access tokens in a database. You will need them to create a session on behalf of the end-user.
6. [Use the Access Token to get a user session](/basics/authentication/oauth/use-the-access-token-to-get-a-user-session) – Authenticate API calls with the received access token, similar to a users API key.
7. **Use the Session Token to authenticate the API calls -** These are the actuall calls your app makes to interact with the API.

{% hint style="info" %}
In practice you'll do steps 1 and 2 only once. Your user will do steps 3 through 5 only ones. From that point on you have stored a access token for your user and whenever you want to act on behalf of the user you use that access token to get a session token and make API calls.
{% endhint %}

OAuth credentials and redirect URLs can also be managed via API endpoints for automated setup.

### **OAuth Scopes & Permissions**

By default, bunq OAuth grants access to:\
✔ Read and create **Monetary Accounts**\
✔ Read **Payments & Transactions**\
✔ Create **Payments** within a user’s accounts\
✔ Create **Draft-Payments** (approval required in the bunq app)\
✔ Assign a **Monetary Account** to a **Card**\
✔ Read, create, and manage **Cards**\
✔ Read and create **Request-Inquiries**\
✔ Read **Request-Responses**

PSD2-licensed developers have scope limitations based on their regulatory role.

### **OAuth Authorization Request**

Your web or mobile app must redirect users to the following authorization URL:

```
https://oauth.bunq.com/auth
```

With the following parameters:

| Parameter       | Description                                                                         |
| --------------- | ----------------------------------------------------------------------------------- |
| `response_type` | Set to `code` for authorization code grant (required)                               |
| `client_id`     | Your OAuth Client ID from bunq Developer (required)                                 |
| `redirect_uri`  | The URL users are redirected to after authorization (must be registered) (required) |
| `state`         | Unique string for request validation (optional)                                     |

For sandbox testing, use:

```
https://oauth.sandbox.bunq.com/auth
```

#### **Example Authorization Request**

```plaintext
https://oauth.bunq.com/auth?response_type=code
&client_id=YOUR_CLIENT_ID
&redirect_uri=https://yourapp.com/callback
&state=unique_string
```

If the user grants access, they are redirected with an authorization code:

```plaintext
https://yourapp.com/callback?code=AUTHORIZATION_CODE&state=unique_string
```

### **Exchanging the Authorization Code for an Access Token**

To obtain an access token, make a `POST` request to:

```
https://api.oauth.bunq.com/v1/token
```

With the following parameters:

| Parameter       | Description                                                        |
| --------------- | ------------------------------------------------------------------ |
| `grant_type`    | Must be `authorization_code` (required)                            |
| `code`          | The authorization code received (required)                         |
| `redirect_uri`  | The same redirect URL used in the authorization request (required) |
| `client_id`     | Your OAuth Client ID (required)                                    |
| `client_secret` | Your OAuth Client Secret (required)                                |

For sandbox testing, use:

```
https://api-oauth.sandbox.bunq.com/v1/token
```

#### **Example Token Exchange Request**

```plaintext
https://api.oauth.bunq.com/v1/token?grant_type=authorization_code
&code=AUTHORIZATION_CODE
&redirect_uri=https://yourapp.com/callback
&client_id=YOUR_CLIENT_ID
&client_secret=YOUR_CLIENT_SECRET
```

#### **Successful Token Response**

```json
{
    "access_token": "ACCESS_TOKEN",
    "token_type": "bearer",
    "state": "unique_string"
}
```

Use the `access_token` for authenticated API requests.

### Store the access\_token and using it to obtain a session

The access token you obtained in the previous step is now your token to act on behalf of the user. This means that the access token you obtained has the same powers as an API key the end-user would generate for himself [API Keys](/basics/authentication/api-keys).

Similar to that user API key you can't use it directly. a user API key is used to create a session, that will result in a session token that you'll use for the actual API calls.\
\
In practice, once you have stored the access token a API interaction will work like this:

1. Your user interacts with your app and now your server wants to retrieve information from bunq
2. You look up the access token you have stored for that user in your database
3. You request a new session at bunq using the access token
4. You use the session token to actually retrieve the information you are after

#### Example response

When you request a session you'll see you'll receive a token and a reference to both the application as well as the end user. Also note the timeout of this session, you may want to keep the tokenin short-term memory for this user for subsequent calls, until it expires.

```
{
  "Response": [
    {
      "Id": {
        "id": 26271775
      }
    },
    {
      "Token": {
        "id": 26271775,
        "created": "2025-06-25 09:03:11.377820",
        "updated": "2025-06-25 09:03:11.377820",
        "token": "0fedc50610b2dcc61997bd817c9b824263ff6fe315234e2a2f0c7e357a3f22a9"
      }
    },
    {
      "UserApiKey": {
        "id": 1964683,
        "created": "2025-06-25 09:03:00.448648",
        "updated": "2025-06-25 09:03:00.448648",
        "requested_by_user": {
          "UserPaymentServiceProvider": {
            "id": 1963873,
            "display_name": "Test PISP AISP 42CF4DAF-898A-44C7-B32C-233CD19D107F",
            "public_nick_name": "Test PISP AISP 42CF4DAF-898A-44C7-B32C-233CD19D107F",
            "avatar": {
              "uuid": "c8559c4f-2d8a-4250-9a78-3f6c66bb69eb",
              "image": [
                {
                  "attachment_public_uuid": "647ba6c1-816e-47e7-93e5-7e2e87a64290",
                  "height": 1023,
                  "width": 1024,
                  "content_type": "image/png",
                  "urls": [
                    {
                      "type": "ORIGINAL",
                      "url": "https://bunq-triage-model-storage-public.s3.eu-central-1.amazonaws.com/bunq_file/File/content/a3fa4840d155cc0f474258d10eb307f5c673eecf7f6d596a56e71c625cbd8cee.png"
                    }
                  ]
                }
              ],
              "anchor_uuid": "a9805ad4-5f59-4045-8254-2218547e886d",
              "style": "NONE"
            },
            "session_timeout": 324000
          }
        },
        "granted_by_user": {
          "UserPerson": {
            "id": 1864430,
            "display_name": "Jodi",
            "public_nick_name": "Jodi",
            "avatar": {
              "uuid": "298692b3-609a-4012-b77c-814017cb74f2",
              "image": [
                {
                  "attachment_public_uuid": "44420a50-7533-4ba2-8f9f-3cf64c807df2",
                  "height": 1024,
                  "width": 1024,
                  "content_type": "image/jpeg",
                  "urls": [
                    {
                      "type": "ORIGINAL",
                      "url": "https://bunq-triage-model-storage-public.s3.eu-central-1.amazonaws.com/bunq_file/File/content/21aaad686c9d07ddbddaa624c1762f21c6b9fe6f0637f80fb5804e691bf43325.jpg"
                    }
                  ]
                }
              ],
              "anchor_uuid": "9b26672b-420a-4067-b2b6-0f6ed19a5932",
              "style": "NONE"
            },
            "session_timeout": 604800
          }
        }
      }
    }
  ]
}
```

### **Making API Calls on behalf of the user**

In the previous section we have shown how you use a access token to receive a session token. This session token is the API key you'll use in requests. For example if you'd want to list the monetary accounts for a user:<br>

```
curl -X GET "https://public-api.sandbox.bunq.com/v1/user/{user_apikey_id}/monetary-account-bank" \
  -H "User-Agent: text" \
  -H "X-Bunq-Client-Authentication: {session_token}" \
  -H "Content-Type: application/json"
```


# Register OAuth Client

Before you can start the OAuth authorization flow with bunq, you need to register an OAuth client. This client represents your application and provides the necessary credentials (`client_id` and `client_secret`) to securely authenticate users.

#### How to Create an OAuth Client

To create an OAuth client, send a `POST` request to:

```
POST /user/{user_id}/oauth-client
```

Replace `{user_id}` with your bunq user ID.

Then it will look like this:<br>

```
curl -X POST "https://api.bunq.com/v1/user/{user_id}/oauth-client" \
  -H "Content-Type: application/json" \
  -H "Cache-Control: no-cache" \
  -H "User-Agent: YourServiceName" \
  -H "X-Bunq-Language: en_US" \
  -H "X-Bunq-Region: nl_NL" \
  -H "X-Bunq-Geolocation: 0 0 0 0 000" \
  -H "X-Bunq-Client-Authentication: your_session_token" \
  -H "X-Bunq-Client-Request-Id: 550e8400-e29b-41d4-a716-446655440000" \
  -H "X-Bunq-Client-Signature: base64_encoded_signature_of_payload" \
  -d '[{
        "status": "ACTIVE",
        "display_name": "My Cool App",
        "redirect_url": "https://myapp.com/oauth/callback"
      }]'
```

Once you have that oauth client registered you can retrieve it's details by repeating the same API call but then as a GET request the response will contain the client\_id and client\_secret. Another item to keep track of is the ID of the oauth client. You may want to use it later in a PUT request to update the redirect URL

{% hint style="warning" %}
Be aware that each bunq user can only have 1 oauth client at a time. You'll get errors if you try to register a second one. As such you want to keep track of the ID so you can update the initial oauth client.
{% endhint %}


# Redirect Users to Authorize your app

Once you’ve successfully registered your OAuth client ( [Register OAuth Client](/basics/authentication/oauth/register-oauth-client)), you will have a **client\_id** and **client\_secret**. These credentials allow your application to request access on behalf of your users.

What we want now is to redirect the end-user to a page where they can give away the access to ther bank account. This is done by redirecting them to the authorization page.

<figure><img src="/files/Y376A7nEwGTwF8tXcCiF" alt=""><figcaption></figcaption></figure>

In your app you'll have a button or step in your signup flow where you send your users to this authorization page when they link your app to their bunq account.

#### Step 1: Redirect Users to the Authorization Page

To begin, redirect your users to bunq’s authorization endpoint where they can grant your app access to their account:

```
https://oauth.bunq.com/auth?response_type=code&client_id={client_id}&redirect_uri={redirect_uri}&state={state}&scope={scope}
```

* `response_type=code` — Indicates that you’re using the authorization code flow.
* `client_id` — Your OAuth client ID.
* `redirect_uri` — Must match the URL registered with your OAuth client.
* `state` — Optional, but highly recommended for CSRF protection.

#### Example Authorization URL

```plaintext
https://oauth.bunq.com/auth?response_type=code&client_id=abc123xyz&redirect_uri=https://myapp.com/oauth/callback&state=xyz789
```

#### What Happens Next?

The user will scan the QR code with their bunq app and grant access to bank accounts. If you want to recreate this step you can use [Android Emulator](/getting-started/tools/android-emulator)to get a sandbox version of the bunq app.

Once the user completes the flow, bunq will redirect back to your `redirect_uri` with a temporary authorization code and the associated state that you sent along:

```
https://myapp.com/oauth/callback?code=AUTH_CODE&state=xyz789
```

You will then exchange this code for an access token to make authenticated API requests on the user’s behalf. We'll handle that in the next section


# Handle the Authorization Code

In the previous steps we have set up an oauth client and learned how users can scan a QR code to grant access to their bunq account.

In this chapter we'll learn how to complete the oauth flow and handle the authorization code.

Once the user authorizes your app, bunq will redirect them to your registered `redirect_uri` with a `code` and `state` parameter this may look like this:

```
https://myapp.com/oauth/callback?code=AUTH_CODE&state=xyz789

```

It's your responsibility to have a endpoint on your server that can catch that callback and retrieve the code and state.

The `state` parameter is a recommended security feature in the OAuth 2.0 flow. It serves two key purposes:

1. **Prevents CSRF Attacks**\
   When initiating the OAuth authorization request, you should generate a unique and unpredictable `state` value and store it temporarily (e.g. in a session, database, or memory store). When the user is redirected back to your `redirect_uri`, you must verify that the returned `state` matches what you originally sent.
2. **Preserves Application Context**\
   You can also use `state` to carry context from your app, such as the ID of the user initiating the flow or the page they started from. This allows you to restore state after the OAuth flow completes.

The **authorization code** is a short-lived, one-time-use credential returned by bunq after a user grants your app permission. It serves as a temporary token that can be securely exchanged for long-term credentials.

### Exchanging the Authorization Code for an Access Token

Once bunq redirects the user to your app with a `code`, you can exchange it for an access token by making a `POST` request to:

```
thttps://oauth.bunq.com/token
```

#### Required Parameters

* `grant_type`: must be `authorization_code`
* `code`: the code you received in the redirect
* `redirect_uri`: must match the one you registered
* `client_id`: your OAuth client ID
* `client_secret`: your OAuth client secret

***

#### Example `cURL` Request

```bash
curl -X POST https://oauth.bunq.com/token \
  -d grant_type=authorization_code \
  -d code=AUTH_CODE \
  -d redirect_uri=https://myapp.com/oauth/callback \
  -d client_id=YOUR_CLIENT_ID \
  -d client_secret=YOUR_CLIENT_SECRET
```

Replace:

* `AUTH_CODE` with the `code` you received from bunq
* `YOUR_CLIENT_ID` with your actual client ID
* `YOUR_CLIENT_SECRET` with your client secret
* `https://myapp.com/oauth/callback` with your registered redirect URI

***

#### Example Successful Response

```json
{"access_token":"8ac6eb1d3a1a36f0bb16afe2b776d5b32f88393da19c06657cf05167da19aa16","token_type":"bearer","state":"hMtbxh3o11-pMiN5E8KOgw"}
```

It's a good idea to store this access\_token in your database and associate it with your end-user. We cannot use the access token to make API calls to bunq directly. Instead we use the Access Token to obtain a session token that we then can use on behalf of the user.

```bash
```

***


# Use the Access Token to get a user session

Once you have received the `access_token` through the OAuth flow, you must use it to create a **session**. This session allows your app to perform authenticated API calls on behalf of the user. This is a equal process to the regular [Start a Session](/tutorials/your-first-payment/creating-the-api-context/start-a-session) flow. However what is different is that you use the end-users access token to get the session.

#### Endpoint

```
POST /v1/session-server
```

#### Purpose

This endpoint creates a new API session using the OAuth access token.

#### Example `cURL` Request

> ⚠️ This example assumes you’ve already signed the payload and have the device token ready.

```bash
bashCopyEditcurl -X POST https://api.bunq.com/v1/session-server \
  -H "Content-Type: application/json" \
  -H "Cache-Control: no-cache" \
  -H "User-Agent: my-app-name" \
  -H "X-Bunq-Language: en_US" \
  -H "X-Bunq-Region: nl_NL" \
  -H "X-Bunq-Geolocation: 0 0 0 0 000" \
  -H "X-Bunq-Client-Authentication: your_device_token" \
  -H "X-Bunq-Client-Signature: BASE64_SIGNATURE_OF_PAYLOAD" \
  -d '{"secret":"USER_ACCESS_TOKEN"}'
```

***

#### ✅ Example Response

```json

{
    "Response": [
        {
            "Id": {
                "id": 25536694
            }
        },
        {
            "Token": {
                "id": 25536694,
                "created": "2025-03-05 16:12:11.918100",
                "updated": "2025-03-05 16:12:11.918100",
                "token": "ce717e3001d979ff5e22bac13508b46e7ad740971d1d75c5371614e546ca8b83"
            }
        },
        {
            "UserPerson": { "id": 1822179,
                            "rest of the user object":...  }
        }
    ]
}
```

***


# Making API calls on behalf of bunq Users

When interacting with the bunq API on behalf of a user, you need two key pieces of information from the **session-server** endpoint: the **end user session token** and the **user API key ID**. The session token represents the end user’s consent and authenticates your requests, while the user API key ID uniquely identifies the API key that your application must use to access that specific user’s data.

The response from the session endpoint is divided into three main sections. **User API Key Info** contains the API key that your application should use for authorized calls. **Requested By** describes your application or Payment Service Provider, including metadata such as display name and session expiration. **Granted By** details the end user who authorized the session, including their display name and session expiration.

It is important to note that you should <mark style="color:$danger;">**not use the normal user ID in API calls**</mark>. All requests must include the session token in the headers and use the user API key ID in the URL to correctly identify the user for the session. This ensures that your application acts on behalf of the user safely and in compliance with bunq’s security model.

For example, to fetch all monetary accounts for a user, you would use the following API call:

```python

#Function assuming you have your full installation set up
def get_monetary_accounts():
    session_token = "fc857727d7c2bff375a0e8cde6e657cec2a5b45d1de489ac3ca20eb033016ad2" 
    end_user_id = "2580769"
    user_api_key_id = "2608880"
    response = requests.get(
        f"https://public-api.sandbox.bunq.com/v1/user/{user_api_key_id}/monetary-account",
        headers={
            "User-Agent": "text",
            "X-Bunq-Client-Authentication": session_token,
            "Content-Type": "application/json"},
    )
    return response.json()
```


# Python Code Example PSD2 - OAuth integration

{% embed url="<https://github.com/two-trick-pony-NL/PSD2-Implementation-for-bunq-API>" %}

In the linked repository you'll find a example integration that has all the moving parts of setting up a bunq server and the oauth flow.

## Setting up the example

In order to use the code example we expect you have a python environment set up. Simply running \`pip install -r requirements.txt\` will take care of setting up dependancies.\
\
If you are a PSD2 user you'll want to create a `UserPaymentServiceProvider` user. These are specific users that have no banking features attached to their bunq account. If that's the case you'll want to run the `create_psd2_user.sh` script. That will register a sandbox PSD2 user for you.

If you're just a bunq business user you can just paste your [API Keys](/basics/authentication/api-keys) in the `main.py` file.<br>

From there you can run \`uvicorn main:app --reload' this will kick off a uvicorn server, serving a FastAPI app on port 8000 to start the server

## Initializing your installation

We prepared a endpoint that you'll have to run 1x it will set up the [Broken mention](broken://pages/O5LLvymwcVpljw1lv1dN). It will also register your [OAuth](/oauth)client and register a [Callback URL OAuth](/api-reference/callback-url-oauth).

You can do so by hitting the localhost:8000/setup\_one\_time it will take a minute.

Finally that script will create a .env file that can be used going forward.

This is a great step to remove your API key from the `main.py` file. From now on it will be read from the `.env` file.

## Using Oauth

You can now go to your localhost:8000/auth endpoint which will trigger a oauth request. If you have the [Android Emulator](/getting-started/tools/android-emulator) on a physical device you'll be able to scan the QR code that is generated and complete the oauth flow.

What happens in the background is:

1. The app created a oauth client in the set up. This allows us to now generate a oauth session. It is a screen with a QR code that the end-user scans with their bunq app
2. In the app the bunq user can select which monetary accounts they grant access to.
3. When the user completes the steps they are redirected to our app. With a authorization code from bunq. This code is immidiatly used by the app to request a access token. The app stores that access token in a database. and assosciates it to a user. We did not build a pretty page but if the oauth flow was successful you'll see a json response with:<br>

   ```
   {"message":"OAuth success","new_user_id":1}
   ```

   This means that we stored a user in our local database with id 1 that has a bunq oauth access token associated to it.
4. So now we can retrieve data on that user. One way to do that is to visit the /docs endpoint. FastApi offers a interactive API documentation. You can also simply visit the localhost:8000/user/1 url. If you request data on behalf of a user then this happens:\
   1\. We look up the bunq access token for that user 1 in our database\
   2\. We request a session token with the access token\
   3\. We make the API call we want with that session token\
   4\. Return the results to the end-user

For instance: if you go to the user/2/accounts endpoint you'll get a summary of the [Monetary Account](/monetary-account)for that user:

```json
{
  "Response": [
    {
      "MonetaryAccountBank": {
        "id": 2083712,
        "created": "2025-04-14 00:00:00.811318",
        "updated": "2025-04-14 00:00:00.811318",
        "alias": [
          {
            "type": "PHONE_NUMBER",
            "value": "+31611019364",
            "name": "+31611019364"
          },
          {
            "type": "EMAIL",
            "value": "testers+1744621019@bunq.com",
            "name": "testers+1744621019@bunq.com"
          },
          {
            "type": "IBAN",
            "value": "NL65BUNQ2090411945",
            "name": "Jodi Walker"
          }
        ],
        "avatar": {
          "uuid": "2ef45ffc-67e9-44ac-a432-a8987c5d4e9d",
          "image": [
            {
              "attachment_public_uuid": "3f3a3d92-b646-463c-ae8d-6a0393e45500",
              "height": 1023,
              "width": 1024,
              "content_type": "image/png",
              "urls": [
                {
                  "type": "ORIGINAL",
                  "url": "https://bunq-triage-model-storage-public.s3.eu-central-1.amazonaws.com/bunq_file/File/content/921ece497cd00f4e0cef3f0f63a962c31cf3f8e35311d127d5a7b23be3d074d5.png"
                }
              ]
            }
          ],
          "anchor_uuid": "5e758d81-add9-4510-89e1-ff2bd5a04146",
          "style": "NONE"
        },
        "balance": {
          "currency": "EUR",
          "value": "4118.00"
        },
        "country": "NL",
        "currency": "EUR",
        "display_name": "J. Walker",
        "daily_limit": {
          "currency": "EUR",
          "value": "5000.00"
        },
        "description": "Main",
        "public_uuid": "5e758d81-add9-4510-89e1-ff2bd5a04146",
        "status": "ACTIVE",
        "sub_status": "NONE",
        "timezone": "europe/amsterdam",
        "user_id": 1864430,
        "monetary_account_profile": null,
        "setting": {
          "color": "#FF7819",
          "icon": null,
          "default_avatar_status": "AVATAR_DEFAULT",
          "restriction_chat": "ALLOW_INCOMING",
          "sdd_expiration_action": "AUTO_ACCEPT"
        },
        "connected_cards": [],
        "budget": [],
        "all_access": [
          {
            "MonetaryAccountAccess": {
              "id": 2268800,
              "created": "2025-04-14 00:00:00.822699",
              "updated": "2025-04-14 00:00:00.822699",
              "access_type": "FULL_PERMANENT"
            }
          }
        ],
        "overdraft_limit": {
          "currency": "EUR",
          "value": "0.00"
        },
        "all_auto_save_id": []
      }
    },
    {
      "MonetaryAccountBank": {
        "id": 2083715,
        "created": "2025-04-14 09:12:06.578796",
        "updated": "2025-06-03 09:48:46.647401",
        "alias": [
          {
            "type": "IBAN",
            "value": "NL55BUNQ2090409118",
            "name": "Jodi Walker"
          }
        ],
        "avatar": {
          "uuid": "f8217171-567f-4034-ac5e-5fc281b611a6",
          "image": [
            {
              "attachment_public_uuid": "fa99b3bf-7121-4d23-9fe9-6ce48ff76285",
              "height": 1024,
              "width": 1024,
              "content_type": "image/jpeg",
              "urls": [
                {
                  "type": "ORIGINAL",
                  "url": "https://bunq-triage-model-storage-public.s3.eu-central-1.amazonaws.com/bunq_file/File/content/1fcb8573f717dc6406dbbb170367f98cbed175ca4d3bee3aeaa772fdcf284c1a.jpg"
                }
              ]
            }
          ],
          "anchor_uuid": "0477b9c1-ff11-4fcb-9bf1-9b84b1b5c27d",
          "style": "NONE"
        },
        "balance": {
          "currency": "EUR",
          "value": "1900.00"
        },
        "country": "NL",
        "currency": "EUR",
        "display_name": "J. Walker",
        "daily_limit": {
          "currency": "EUR",
          "value": "5000.00"
        },
        "description": "Main",
        "public_uuid": "0477b9c1-ff11-4fcb-9bf1-9b84b1b5c27d",
        "status": "ACTIVE",
        "sub_status": "NONE",
        "timezone": "europe/amsterdam",
        "user_id": 1864430,
        "monetary_account_profile": null,
        "setting": {
          "color": "#ff7819",
          "icon": "FINANCE",
          "default_avatar_status": "AVATAR_ICON",
          "restriction_chat": "ALLOW_INCOMING",
          "sdd_expiration_action": "AUTO_ACCEPT"
        },
        "connected_cards": [],
        "budget": [],
        "all_access": [
          {
            "MonetaryAccountAccess": {
              "id": 2268802,
              "created": "2025-04-14 09:12:06.599804",
              "updated": "2025-04-14 09:12:06.599804",
              "access_type": "FULL_PERMANENT"
            }
          }
        ],
        "overdraft_limit": {
          "currency": "EUR",
          "value": "0.00"
        },
        "all_auto_save_id": []
      }
    },
    {
      "MonetaryAccountBank": {
        "id": 2083719,
        "created": "2025-04-14 09:12:58.553604",
        "updated": "2025-04-14 09:14:06.776031",
        "alias": [
          {
            "type": "IBAN",
            "value": "NL17BUNQ2090415428",
            "name": "Jodi Walker"
          }
        ],
        "avatar": {
          "uuid": "48bb720a-6baf-4482-a78c-b8592e5fde08",
          "image": [
            {
              "attachment_public_uuid": "0e76b824-36ef-4680-9554-0c2da11f6ed9",
              "height": 1024,
              "width": 1024,
              "content_type": "image/jpeg",
              "urls": [
                {
                  "type": "ORIGINAL",
                  "url": "https://bunq-triage-model-storage-public.s3.eu-central-1.amazonaws.com/bunq_file/File/content/4c90f8a578279abd53b06840a4dd619009c2f4b9ddc4342dbf12e4fbf11bbd4a.jpg"
                }
              ]
            }
          ],
          "anchor_uuid": "fbb765b0-2eef-4c91-8060-37950b65694a",
          "style": "NONE"
        },
        "balance": {
          "currency": "EUR",
          "value": "954.00"
        },
        "country": "NL",
        "currency": "EUR",
        "display_name": "J. Walker",
        "daily_limit": {
          "currency": "EUR",
          "value": "5000.00"
        },
        "description": "Car",
        "public_uuid": "fbb765b0-2eef-4c91-8060-37950b65694a",
        "status": "ACTIVE",
        "sub_status": "NONE",
        "timezone": "europe/amsterdam",
        "user_id": 1864430,
        "monetary_account_profile": null,
        "setting": {
          "color": "#47bfff",
          "icon": "TRANSPORT",
          "default_avatar_status": "AVATAR_ICON",
          "restriction_chat": "ALLOW_INCOMING",
          "sdd_expiration_action": "AUTO_ACCEPT"
        },
        "connected_cards": [],
        "budget": [],
        "all_access": [
          {
            "MonetaryAccountAccess": {
              "id": 2268806,
              "created": "2025-04-14 09:12:58.575972",
              "updated": "2025-04-14 09:12:58.575972",
              "access_type": "FULL_PERMANENT"
            }
          }
        ],
        "overdraft_limit": {
          "currency": "EUR",
          "value": "0.00"
        },
        "all_auto_save_id": []
      }
    },
    {
      "MonetaryAccountBank": {
        "id": 2083721,
        "created": "2025-04-14 09:13:51.146459",
        "updated": "2025-04-14 09:13:51.146459",
        "alias": [
          {
            "type": "IBAN",
            "value": "NL09BUNQ2090405775",
            "name": "Jodi Walker"
          }
        ],
        "avatar": {
          "uuid": "5ac4305a-846b-46c0-bff7-b383db308acf",
          "image": [
            {
              "attachment_public_uuid": "b312f25b-fb29-4fd0-a971-79c6eaf8b8e0",
              "height": 1024,
              "width": 1024,
              "content_type": "image/jpeg",
              "urls": [
                {
                  "type": "ORIGINAL",
                  "url": "https://bunq-triage-model-storage-public.s3.eu-central-1.amazonaws.com/bunq_file/File/content/854e26c59d732cf32e7187f7ee4bcc11f8415d06f99ed59780fcc21883bd0f62.jpg"
                }
              ]
            }
          ],
          "anchor_uuid": "81802c69-3039-4d73-8138-da6e43acb7f0",
          "style": "NONE"
        },
        "balance": {
          "currency": "EUR",
          "value": "1643.00"
        },
        "country": "NL",
        "currency": "EUR",
        "display_name": "J. Walker",
        "daily_limit": {
          "currency": "EUR",
          "value": "5000.00"
        },
        "description": "Medical",
        "public_uuid": "81802c69-3039-4d73-8138-da6e43acb7f0",
        "status": "ACTIVE",
        "sub_status": "NONE",
        "timezone": "europe/amsterdam",
        "user_id": 1864430,
        "monetary_account_profile": null,
        "setting": {
          "color": "#ff2d55",
          "icon": "HEALTHCARE",
          "default_avatar_status": "AVATAR_ICON",
          "restriction_chat": "ALLOW_INCOMING",
          "sdd_expiration_action": "AUTO_ACCEPT"
        },
        "connected_cards": [],
        "budget": [],
        "all_access": [
          {
            "MonetaryAccountAccess": {
              "id": 2268808,
              "created": "2025-04-14 09:13:51.163604",
              "updated": "2025-04-14 09:13:51.163604",
              "access_type": "FULL_PERMANENT"
            }
          }
        ],
        "overdraft_limit": {
          "currency": "EUR",
          "value": "0.00"
        },
        "all_auto_save_id": []
      }
    }
  ],
  "Pagination": {
    "future_url": null,
    "newer_url": null,
    "older_url": null
  }
}
```

<br>


# bunq API Objects

No matter what kind of integration you’re building, you’ll be working with a few essential objects that make up the bunq ecosystem. While the general idea behind each one is straightforward, some names or relationships might seem a little unexpected—mainly because the public API is built on top of the same system that powers the bunq app itself.

Here are the main objects you’ll need to get familiar with (by ID):

* [**User**](/basics/bunq-api-objects/user)
* [**MonetaryAccount**](/basics/bunq-api-objects/monetary-account)
* [**Payment**](/basics/bunq-api-objects/payment)
* [**RequestInquiry**](/basics/bunq-api-objects/requestinquiry)
* [**Card**](/basics/bunq-api-objects/card)
* [**Attachment**](/basics/bunq-api-objects/attachment-and-note-attachment)
* [**NoteAttachment**](/basics/bunq-api-objects/attachment-and-note-attachment)

Let’s take a closer look at each of them and see how they fit together.

<figure><img src="/files/ERuYzkSqknady9v2v8As" alt=""><figcaption><p>Entity relation graph</p></figcaption></figure>


# User

At bunq, everything revolves around the **User**—it's the foundation of our app, services, and API. That’s why almost every endpoint you’ll use starts with `/user`.

There are **three types of users**, each with its own endpoints, permissions, and properties:

* **`user-person`** – for individual users (`/user-person`)
* **`user-company`** – for business users (`/user-company`)
* **`user-payment-service-provider`** – for PSD2-certified third-party providers (`/user-payment-service-provider`)

The general `/user` endpoint acts as a smart wrapper over all these types. So when you make a `GET /user` request after opening a session, you’ll receive your specific user ID and type—this is usually one of the first steps in any integration.

#### **👤 `user-person` and `user-company`**

These are regular bunq users with subscriptions. If you're a bunq customer, you'll fall into one of these categories. As a user, you can:

* Create **monetary accounts** (our term for bank accounts)
* Order **cards**
* Make and receive **payments**

These objects work together and have subtypes, which we’ll explain in the next sections.

#### **🏦 `user-payment-service-provider`**

This type of user is for companies certified under **PSD2** regulations. When a company registers a valid **eIDAS certificate**, we create a `user-payment-service-provider` account and return an API key.

These users:

* **Don’t have access to the bunq app**
* Can only operate via the API
* Can act **on behalf of bunq users**, within the scope of their PSD2 role:
  * AISP (Account Information Service Provider)
  * PISP (Payment Initiation Service Provider)
  * CBPII (Card-Based Payment Instrument Issuer)

## Example:

```
{
    "Response": [
        {
            "UserPerson": {
                "id": 2040503,
                "created": "2025-07-21 09:14:18.001154",
                "updated": "2025-07-21 09:14:18.048752",
                "status": "ACTIVE",
                "sub_status": "NONE",
                "public_uuid": "e52b4912-c314-423a-80a4-939f4c992d14",
                "display_name": "A. Visser",
                "public_nick_name": "Ayako",
                "language": "en_US",
                "region": "en_US",
                "session_timeout": 604800,
                "daily_limit_without_confirmation_login": {
                    "currency": "EUR",
                    "value": "250.00"
                },
                "relations": [],
                "alias": [
                    {
                        "type": "PHONE_NUMBER",
                        "value": "+31619257130",
                        "name": "+31619257130"
                    },
                    {
                        "type": "EMAIL",
                        "value": "test+41478d1b-7b9c-4d8e-9e32-860fa4cfde24@bunq.com",
                        "name": "test+41478d1b-7b9c-4d8e-9e32-860fa4cfde24@bunq.com"
                    }
                ],
                "avatar": {
                    "uuid": "9debf55b-70b1-4d43-8d51-d6e7bd42fb5b",
                    "image": [
                        {
                            "attachment_public_uuid": "4b7e0d1d-9167-48ac-990a-70e342c87812",
                            "height": 126,
                            "width": 200,
                            "content_type": "image/jpeg",
                            "urls": [
                                {
                                    "type": "ORIGINAL",
                                    "url": "https://bunq-triage-model-storage-public.s3.eu-central-1.amazonaws.com/bunq_file/File/content/6979a145b7ea9ecc3459358122cb560608f02d36d4b8cd6b770f50e36aa35512.jpg"
                                }
                            ]
                        }
                    ],
                    "anchor_uuid": "e52b4912-c314-423a-80a4-939f4c992d14",
                    "style": "NONE"
                },
                "tax_resident": null,
                "notification_filters": [
                    {
                        "notification_delivery_method": "PUSH",
                        "category": "BADGE"
                    },
                    {
                        "notification_delivery_method": "PUSH",
                        "category": "BANK_SWITCH_SERVICE"
                    },
                    {
                        "notification_delivery_method": "PUSH",
                        "category": "BANK_SWITCH_SERVICE_PAYMENT"
                    },
                    {
                        "notification_delivery_method": "PUSH",
                        "category": "BILLING"
                    },
                    {
                        "notification_delivery_method": "PUSH",
                        "category": "BUNQME_TAB"
                    },
                    {
                        "notification_delivery_method": "PUSH",
                        "category": "BUNQME_FUNDRAISER"
                    },
                    {
                        "notification_delivery_method": "PUSH",
                        "category": "CARD_TRANSACTION_FAILED"
                    },
                    {
                        "notification_delivery_method": "PUSH",
                        "category": "CARD_TRANSACTION_SUCCESSFUL"
                    },
                    {
                        "notification_delivery_method": "PUSH",
                        "category": "CHAT"
                    },
                    {
                        "notification_delivery_method": "PUSH",
                        "category": "CO_OWNER_INVITE"
                    },
                    {
                        "notification_delivery_method": "PUSH",
                        "category": "DRAFT_PAYMENT"
                    },
                    {
                        "notification_delivery_method": "PUSH",
                        "category": "FEATURE_ANNOUNCEMENT"
                    },
                    {
                        "notification_delivery_method": "PUSH",
                        "category": "FRIEND_SIGN_UP"
                    },
                    {
                        "notification_delivery_method": "PUSH",
                        "category": "PACK"
                    },
                    {
                        "notification_delivery_method": "PUSH",
                        "category": "IDEAL"
                    },
                    {
                        "notification_delivery_method": "PUSH",
                        "category": "SOFORT"
                    },
                    {
                        "notification_delivery_method": "PUSH",
                        "category": "INVITE_USER"
                    },
                    {
                        "notification_delivery_method": "PUSH",
                        "category": "MONETARY_ACCOUNT_JOINT"
                    },
                    {
                        "notification_delivery_method": "PUSH",
                        "category": "MONETARY_ACCOUNT_PROFILE"
                    },
                    {
                        "notification_delivery_method": "PUSH",
                        "category": "MUTATION"
                    },
                    {
                        "notification_delivery_method": "PUSH",
                        "category": "PAYMENT"
                    },
                    {
                        "notification_delivery_method": "PUSH",
                        "category": "PROMOTION"
                    },
                    {
                        "notification_delivery_method": "PUSH",
                        "category": "REQUEST"
                    },
                    {
                        "notification_delivery_method": "PUSH",
                        "category": "SCHEDULE_RESULT"
                    },
                    {
                        "notification_delivery_method": "PUSH",
                        "category": "SCHEDULE_STATUS"
                    },
                    {
                        "notification_delivery_method": "PUSH",
                        "category": "SHARE"
                    },
                    {
                        "notification_delivery_method": "PUSH",
                        "category": "SUPPORT"
                    },
                    {
                        "notification_delivery_method": "PUSH",
                        "category": "TAB_RESULT"
                    },
                    {
                        "notification_delivery_method": "PUSH",
                        "category": "TAX_IDENTIFICATION_NUMBER_WARNING"
                    },
                    {
                        "notification_delivery_method": "PUSH",
                        "category": "USE_RESPONSE"
                    },
                    {
                        "notification_delivery_method": "PUSH",
                        "category": "USE_RESPONSE_NATIVE_COMMENT"
                    },
                    {
                        "notification_delivery_method": "PUSH",
                        "category": "USE_RESPONSE_NATIVE_TOPIC"
                    },
                    {
                        "notification_delivery_method": "PUSH",
                        "category": "USER_APPROVAL"
                    },
                    {
                        "notification_delivery_method": "PUSH",
                        "category": "WHITELIST"
                    },
                    {
                        "notification_delivery_method": "PUSH",
                        "category": "WHITELIST_RESULT"
                    },
                    {
                        "notification_delivery_method": "PUSH",
                        "category": "SLICE_BADGE"
                    },
                    {
                        "notification_delivery_method": "PUSH",
                        "category": "SLICE_CHAT"
                    },
                    {
                        "notification_delivery_method": "PUSH",
                        "category": "SLICE_REGISTRY_ENTRY"
                    },
                    {
                        "notification_delivery_method": "PUSH",
                        "category": "SLICE_REGISTRY_MEMBERSHIP"
                    },
                    {
                        "notification_delivery_method": "PUSH",
                        "category": "SLICE_REGISTRY_SETTLEMENT"
                    },
                    {
                        "notification_delivery_method": "PUSH",
                        "category": "EXPERIENCE"
                    },
                    {
                        "notification_delivery_method": "PUSH",
                        "category": "ACHIEVEMENT"
                    },
                    {
                        "notification_delivery_method": "PUSH",
                        "category": "DIRECTOR"
                    },
                    {
                        "notification_delivery_method": "PUSH",
                        "category": "POPUP_NOTIFICATION"
                    },
                    {
                        "notification_delivery_method": "PUSH",
                        "category": "SUBSCRIPTION_TRIAL"
                    },
                    {
                        "notification_delivery_method": "PUSH",
                        "category": "CARD"
                    },
                    {
                        "notification_delivery_method": "PUSH",
                        "category": "FLARUM"
                    },
                    {
                        "notification_delivery_method": "PUSH",
                        "category": "REMINDER_REQUEST"
                    },
                    {
                        "notification_delivery_method": "PUSH",
                        "category": "INSTANT_PAYMENT"
                    },
                    {
                        "notification_delivery_method": "PUSH",
                        "category": "PREMIUM_LIMITED"
                    },
                    {
                        "notification_delivery_method": "PUSH",
                        "category": "CARD_TRANSACTION_UPDATED"
                    },
                    {
                        "notification_delivery_method": "PUSH",
                        "category": "REFERRAL"
                    },
                    {
                        "notification_delivery_method": "PUSH",
                        "category": "REWARD"
                    },
                    {
                        "notification_delivery_method": "PUSH",
                        "category": "SELECT_USER"
                    },
                    {
                        "notification_delivery_method": "PUSH",
                        "category": "INTEREST"
                    },
                    {
                        "notification_delivery_method": "PUSH",
                        "category": "REMINDER_USER_IDENTIFICATION_VERIFICATION"
                    },
                    {
                        "notification_delivery_method": "PUSH",
                        "category": "JOINT_MEMBERSHIP"
                    },
                    {
                        "notification_delivery_method": "PUSH",
                        "category": "RISK_INFORMATION"
                    },
                    {
                        "notification_delivery_method": "PUSH",
                        "category": "CHECKOUT_MERCHANT"
                    },
                    {
                        "notification_delivery_method": "PUSH",
                        "category": "BARZAHLEN"
                    },
                    {
                        "notification_delivery_method": "PUSH",
                        "category": "LOCATION_EVENT"
                    },
                    {
                        "notification_delivery_method": "PUSH",
                        "category": "TREE"
                    },
                    {
                        "notification_delivery_method": "PUSH",
                        "category": "CARD_TRANSACTION_REFUND"
                    },
                    {
                        "notification_delivery_method": "PUSH",
                        "category": "USER_REVIEW"
                    },
                    {
                        "notification_delivery_method": "PUSH",
                        "category": "CARD_DYNAMIC_CURRENCY_CONVERSION_WARNING"
                    },
                    {
                        "notification_delivery_method": "PUSH",
                        "category": "BANCONTACT_MERCHANT"
                    },
                    {
                        "notification_delivery_method": "PUSH",
                        "category": "TRIBE_GREEN"
                    },
                    {
                        "notification_delivery_method": "PUSH",
                        "category": "TRIBE_RANK_GREEN"
                    },
                    {
                        "notification_delivery_method": "PUSH",
                        "category": "GREEN_O_METER"
                    },
                    {
                        "notification_delivery_method": "PUSH",
                        "category": "CARD_TRANSACTION_TOKENIZED"
                    },
                    {
                        "notification_delivery_method": "PUSH",
                        "category": "MONETARY_ACCOUNT_BALANCE"
                    },
                    {
                        "notification_delivery_method": "PUSH",
                        "category": "SUPPORT_ANNOUNCEMENT"
                    },
                    {
                        "notification_delivery_method": "PUSH",
                        "category": "GIROPAY_MERCHANT"
                    },
                    {
                        "notification_delivery_method": "PUSH",
                        "category": "OAUTH"
                    },
                    {
                        "notification_delivery_method": "PUSH",
                        "category": "RELATION_USER_REQUEST"
                    },
                    {
                        "notification_delivery_method": "PUSH",
                        "category": "TRIBE_COMMON_GOAL"
                    },
                    {
                        "notification_delivery_method": "PUSH",
                        "category": "FULFILLMENT"
                    },
                    {
                        "notification_delivery_method": "PUSH",
                        "category": "INVITE_REFERRAL_STREAK"
                    },
                    {
                        "notification_delivery_method": "PUSH",
                        "category": "OPEN_BANKING"
                    },
                    {
                        "notification_delivery_method": "PUSH",
                        "category": "CARD_EXPIRY"
                    },
                    {
                        "notification_delivery_method": "PUSH",
                        "category": "SAVINGS_BUDGET_WARNING"
                    },
                    {
                        "notification_delivery_method": "PUSH",
                        "category": "IDIN"
                    },
                    {
                        "notification_delivery_method": "PUSH",
                        "category": "SECURE_CODE"
                    },
                    {
                        "notification_delivery_method": "PUSH",
                        "category": "TRANSIT_TRIP"
                    },
                    {
                        "notification_delivery_method": "PUSH",
                        "category": "FEATURE_SUGGESTION"
                    },
                    {
                        "notification_delivery_method": "PUSH",
                        "category": "USER_RECORDING"
                    },
                    {
                        "notification_delivery_method": "PUSH",
                        "category": "USER_IMPERSONATION"
                    },
                    {
                        "notification_delivery_method": "PUSH",
                        "category": "INSIGHT_SUBSCRIPTION"
                    },
                    {
                        "notification_delivery_method": "PUSH",
                        "category": "SUBSCRIPTION_SWITCH"
                    },
                    {
                        "notification_delivery_method": "PUSH",
                        "category": "MASTERCARD_IDENTITY_CHECK"
                    },
                    {
                        "notification_delivery_method": "PUSH",
                        "category": "PAYSAFECASH"
                    },
                    {
                        "notification_delivery_method": "PUSH",
                        "category": "SAVINGS_SMART_SAVE"
                    },
                    {
                        "notification_delivery_method": "PUSH",
                        "category": "SAVINGS_AUTO_ROUTE"
                    },
                    {
                        "notification_delivery_method": "PUSH",
                        "category": "BUNQME_TAB_RESULT_INQUIRY"
                    },
                    {
                        "notification_delivery_method": "PUSH",
                        "category": "BUNQME_TAB_RESULT_RESPONSE"
                    },
                    {
                        "notification_delivery_method": "PUSH",
                        "category": "TRANSLINK"
                    },
                    {
                        "notification_delivery_method": "PUSH",
                        "category": "PAYMENT_INVESTIGATION"
                    },
                    {
                        "notification_delivery_method": "PUSH",
                        "category": "CARD_REPLACEMENT"
                    },
                    {
                        "notification_delivery_method": "PUSH",
                        "category": "MARKETING_COMMUNICATION"
                    },
                    {
                        "notification_delivery_method": "PUSH",
                        "category": "JACKPOT"
                    },
                    {
                        "notification_delivery_method": "PUSH",
                        "category": "JACKPOT_INVITE"
                    },
                    {
                        "notification_delivery_method": "PUSH",
                        "category": "INCIDENT_USER_COMMUNICATION"
                    },
                    {
                        "notification_delivery_method": "PUSH",
                        "category": "RELATION_USER_SUGGESTION"
                    },
                    {
                        "notification_delivery_method": "PUSH",
                        "category": "DEBT"
                    },
                    {
                        "notification_delivery_method": "PUSH",
                        "category": "WHEEL_OF_FORTUNE_PRIZE"
                    },
                    {
                        "notification_delivery_method": "PUSH",
                        "category": "CASHBACK"
                    },
                    {
                        "notification_delivery_method": "PUSH",
                        "category": "CONTACT_PHONEBOOK_SIGNUP"
                    },
                    {
                        "notification_delivery_method": "PUSH",
                        "category": "REGISTRY_FEED"
                    },
                    {
                        "notification_delivery_method": "PUSH",
                        "category": "PAYDAY"
                    },
                    {
                        "notification_delivery_method": "PUSH",
                        "category": "CARD_TOKENIZATION"
                    },
                    {
                        "notification_delivery_method": "PUSH",
                        "category": "CARD_TRANSACTION_AMOUNT_UPDATED"
                    },
                    {
                        "notification_delivery_method": "PUSH",
                        "category": "ADYEN_MERCHANT"
                    },
                    {
                        "notification_delivery_method": "PUSH",
                        "category": "ADYEN_CARD_TRANSACTION"
                    },
                    {
                        "notification_delivery_method": "PUSH",
                        "category": "USER_VISITED_DEVICE"
                    },
                    {
                        "notification_delivery_method": "PUSH",
                        "category": "PAYMENT_DELAYED"
                    },
                    {
                        "notification_delivery_method": "PUSH",
                        "category": "TRAVEL_INSURANCE"
                    },
                    {
                        "notification_delivery_method": "PUSH",
                        "category": "PAYMENT_SUSPENDED"
                    },
                    {
                        "notification_delivery_method": "PUSH",
                        "category": "USER_SETTING_PAYMENT_SUSPENDED"
                    },
                    {
                        "notification_delivery_method": "PUSH",
                        "category": "PAYMENT_UPDATED"
                    },
                    {
                        "notification_delivery_method": "PUSH",
                        "category": "CREDIT_LINE_OFFER"
                    },
                    {
                        "notification_delivery_method": "PUSH",
                        "category": "CREDIT_LINE_REPAYMENT"
                    },
                    {
                        "notification_delivery_method": "PUSH",
                        "category": "REMINDER_WHEEL_OF_FORTUNE"
                    },
                    {
                        "notification_delivery_method": "PUSH",
                        "category": "GINMON_ACCOUNT"
                    },
                    {
                        "notification_delivery_method": "PUSH",
                        "category": "GINMON_TRANSACTION"
                    },
                    {
                        "notification_delivery_method": "PUSH",
                        "category": "DEBT_PREVENTION"
                    },
                    {
                        "notification_delivery_method": "PUSH",
                        "category": "USER_MINOR_MATURITY"
                    },
                    {
                        "notification_delivery_method": "PUSH",
                        "category": "POINT"
                    },
                    {
                        "notification_delivery_method": "PUSH",
                        "category": "POINT_SUMMARY"
                    },
                    {
                        "notification_delivery_method": "PUSH",
                        "category": "GINMON_ACCOUNT_VALUATION_WEEKLY"
                    },
                    {
                        "notification_delivery_method": "PUSH",
                        "category": "USER_INFORMATION_INQUIRY"
                    },
                    {
                        "notification_delivery_method": "PUSH",
                        "category": "BIZUM_TRANSACTION"
                    },
                    {
                        "notification_delivery_method": "PUSH",
                        "category": "BIZUM_REQUEST"
                    },
                    {
                        "notification_delivery_method": "PUSH",
                        "category": "ASSISTANT_CONVERSATION"
                    },
                    {
                        "notification_delivery_method": "PUSH",
                        "category": "CARD_PERSONALIZATION"
                    },
                    {
                        "notification_delivery_method": "PUSH",
                        "category": "SIM_VIRTUAL"
                    },
                    {
                        "notification_delivery_method": "PUSH",
                        "category": "ASSISTANT_ACTION_SUGGESTION"
                    },
                    {
                        "notification_delivery_method": "PUSH",
                        "category": "KRAKEN_TRANSACTION"
                    },
                    {
                        "notification_delivery_method": "PUSH",
                        "category": "KRAKEN_ASSET_ALERT"
                    },
                    {
                        "notification_delivery_method": "PUSH",
                        "category": "KRAKEN_ACCOUNT"
                    },
                    {
                        "notification_delivery_method": "PUSH",
                        "category": "COMPANY_EMPLOYEE_CARD"
                    },
                    {
                        "notification_delivery_method": "PUSH",
                        "category": "SIM_VIRTUAL_PRODUCT_ORDER"
                    }
                ],
                "address_main": {
                    "street": "Doyle Close",
                    "house_number": "638",
                    "postal_code": "5269 FF",
                    "city": "Almere",
                    "country": "NL",
                    "province": null,
                    "extra": null,
                    "mailbox_name": null,
                    "is_user_address_updated": false,
                    "id": 12105450,
                    "created": "2025-07-21 09:14:18.307804",
                    "updated": "2025-07-21 09:14:18.307804"
                },
                "address_postal": {
                    "street": "Doyle Close",
                    "house_number": "638",
                    "postal_code": "5269 FF",
                    "city": "Almere",
                    "country": "NL",
                    "province": null,
                    "extra": null,
                    "mailbox_name": "Valentin Mitchell",
                    "is_user_address_updated": false,
                    "id": 12105451,
                    "created": "2025-07-21 09:14:18.318763",
                    "updated": "2025-07-21 09:14:18.318763"
                },
                "address_shipping": null,
                "first_name": "Ayako",
                "middle_name": "",
                "last_name": "Visser",
                "legal_name": "Ayako Visser",
                "date_of_birth": "1987-01-15",
                "place_of_birth": "Almere",
                "country_of_birth": "NL",
                "nationality": "NL",
                "all_nationality": [
                    "NL"
                ],
                "gender": "FEMALE",
                "version_terms_of_service": "1",
                "deny_reason": null,
                "document_issuing_authority": null,
                "document_expiry_date": "2035-07-21 00:00:00.000000",
                "document_status": "ACTIVE",
                "is_primary_document": true,
                "customer": {
                    "id": 1748581,
                    "created": "2025-07-21 09:14:18.907612",
                    "updated": "2025-07-21 09:14:18.907612",
                    "billing_account_id": 2428053,
                    "invoice_notification_preference": "NONE"
                },
                "customer_limit": {
                    "limit_monetary_account": 25,
                    "limit_monetary_account_remaining": 25,
                    "limit_card_debit_maestro": 1,
                    "limit_card_debit_mastercard": 2,
                    "limit_card_wildcard": 3,
                    "limit_card_debit_wildcard": 3,
                    "limit_card_debit_maestro_virtual_subscription": 5,
                    "limit_card_debit_maestro_virtual_total": 0,
                    "limit_card_debit_mastercard_virtual_subscription": 5,
                    "limit_card_debit_mastercard_virtual_total": 0,
                    "limit_card_replacement": 1,
                    "limit_amount_monthly": null,
                    "spent_amount_monthly": null,
                    "limit_card_credit_mastercard": 1
                },
                "billing_contract": [
                    {
                        "BillingContractSubscription": {
                            "id": 19330950,
                            "created": "2025-07-21 09:14:18.333150",
                            "updated": "2025-07-21 09:14:18.333150",
                            "contract_date_start": "2025-07-21",
                            "contract_date_end": null,
                            "contract_version": 1,
                            "subscription_type": "PERSON_PREMIUM_V1",
                            "subscription_type_downgrade": null,
                            "status": "ACTIVE",
                            "sub_status": "NONE"
                        }
                    }
                ],
                "pack_membership": null,
                "premium_trial": null
            }
        }
    ]
}
```


# Monetary Account

The **MonetaryAccount** is the second most important object you’ll work with—it’s where the money lives. Think of it as a **bank account**, **sub-account**, or even a **pot**, depending on how you use it. Unlike wallets (which are tied directly to cards), a monetary account is always connected to a **legal owner**—a bunq user.

Most financial operations in the bunq API—like making payments or requesting money—require a `monetary-account-id`. You can retrieve it by listing all accounts under a user:\
`GET /user/{userId}/monetary-account`

#### **🏦 Types of Monetary Accounts**

Just like user types, monetary accounts come in a few flavors—each with its own endpoint to make things easier to manage:

* **MonetaryAccountBank** – classic personal or business bank accounts\
  `GET /user/{userId}/monetary-account-bank`
* **MonetaryAccountSavings** – regular or auto-savings accounts (including VAT accounts)\
  `GET /user/{userId}/monetary-account-savings`
* **MonetaryAccountJoint** – shared accounts with other bunq users (they’re also legal co-owners)\
  `GET /user/{userId}/monetary-account-joint`

Each type works slightly differently, but they all follow the same structure and principles. Once you know how to use one, the others are easy to pick up.

## Example

```json
{
    "Response": [
        {
            "MonetaryAccountBank": {
                "id": 2428053,
                "created": "2025-07-21 09:14:18.661006",
                "updated": "2025-07-21 09:14:18.661006",
                "alias": [
                    {
                        "type": "PHONE_NUMBER",
                        "value": "+31619257130",
                        "name": "+31619257130"
                    },
                    {
                        "type": "EMAIL",
                        "value": "test+41478d1b-7b9c-4d8e-9e32-860fa4cfde24@bunq.com",
                        "name": "test+41478d1b-7b9c-4d8e-9e32-860fa4cfde24@bunq.com"
                    },
                    {
                        "type": "IBAN",
                        "value": "NL51BUNQ2093937468",
                        "name": "Ayako Visser"
                    }
                ],
                "avatar": {
                    "uuid": "659e794e-f55e-4cfe-9f6d-465622cf43da",
                    "image": [
                        {
                            "attachment_public_uuid": "523b48a0-7a77-4598-9920-86f26135fef0",
                            "height": 1023,
                            "width": 1024,
                            "content_type": "image/png",
                            "urls": [
                                {
                                    "type": "ORIGINAL",
                                    "url": "https://bunq-triage-model-storage-public.s3.eu-central-1.amazonaws.com/bunq_file/File/content/921ece497cd00f4e0cef3f0f63a962c31cf3f8e35311d127d5a7b23be3d074d5.png"
                                }
                            ]
                        }
                    ],
                    "anchor_uuid": "e2947f01-1ae1-4be3-a9e4-6605476ff843",
                    "style": "NONE"
                },
                "balance": {
                    "currency": "EUR",
                    "value": "0.00"
                },
                "balance_converted": {
                    "currency": "EUR",
                    "value": "0.00"
                },
                "country": "NL",
                "currency": "EUR",
                "display_name": "A. Visser",
                "daily_limit": {
                    "currency": "EUR",
                    "value": "5000.00"
                },
                "description": "Main",
                "public_uuid": "e2947f01-1ae1-4be3-a9e4-6605476ff843",
                "status": "ACTIVE",
                "sub_status": "NONE",
                "timezone": "europe/amsterdam",
                "user_id": 2040503,
                "monetary_account_profile": {
                    "profile_fill": {
                        "status": "ACTIVE",
                        "balance_preferred": {
                            "currency": "EUR",
                            "value": "100.00"
                        },
                        "balance_threshold_low": {
                            "currency": "EUR",
                            "value": "50.00"
                        }
                    },
                    "profile_drain": null,
                    "profile_action_required": "NO_ACTION_NEEDED",
                    "profile_amount_required": {
                        "currency": "EUR",
                        "value": "0.00"
                    }
                },
                "setting": {
                    "color": "#FF7819",
                    "icon": null,
                    "default_avatar_status": "AVATAR_DEFAULT",
                    "restriction_chat": "ALLOW_INCOMING",
                    "sdd_expiration_action": "AUTO_ACCEPT"
                },
                "connected_cards": [],
                "budget": [],
                "all_access": [
                    {
                        "MonetaryAccountAccess": {
                            "id": 2595530,
                            "created": "2025-07-21 09:14:18.742179",
                            "updated": "2025-07-21 09:14:18.742179",
                            "access_type": "FULL_PERMANENT"
                        }
                    }
                ],
                "overdraft_limit": {
                    "currency": "EUR",
                    "value": "0.00"
                },
                "all_auto_save_id": []
            }
        }
    ]
}
```


# Payment

Since all money is stored in **MonetaryAccounts**, every transaction—whether sending or receiving—flows through them. The **Payment** object represents these transactions, and includes both **incoming** and **outgoing** payments. When you list a user’s payments, you’ll get the full history of both.

While you can’t control incoming payments (surprise deposits are always welcome, right?), you *can* create outgoing payments in a few different ways using the API:

#### **🚀 Types of Outgoing Payments**

All outgoing payments are based on the core `Payment` object but are handled through different endpoints depending on the type:

* **Single Payment**\
  A standard payment to one counterparty.\
  `POST /user/{userId}/monetary-account/{monetaryAccountId}/payment`
* **Payment Batch**\
  A list of payments to be sent in one go.\
  `POST /user/{userId}/monetary-account/{monetaryAccountId}/payment-batch`
* **Draft Payment**\
  A payment that needs to be reviewed and accepted by the user.\
  `POST /user/{userId}/monetary-account/{monetaryAccountId}/draft-payment`
* **Scheduled Payment**\
  A payment set to be sent in the future.\
  `POST /user/{userId}/monetary-account/{monetaryAccountId}/schedule-payment`
* **Scheduled Payment Batch**\
  Multiple scheduled payments bundled together.\
  `POST /user/{userId}/monetary-account/{monetaryAccountId}/schedule-payment-batch`

{% hint style="info" %}
🔁 *Draft scheduled payments* (recurring payments pending approval) also fall under the `DraftPayment` endpoint, depending on how you want your UX to behave.
{% endhint %}

#### **🧾 What You Need to Create a Payment**

To make any outgoing payment, you’ll need two things:

* The **amount** to send
* The **receiver** (which we call `counterparty_alias`)

You can identify the receiver using one of the following pointer types:

* `EMAIL`
* `PHONE_NUMBER`
* `IBAN`

## Example

```json
{
    "Response": [
        {
            "Payment": {
                "id": 26174613,
                "created": "2025-07-21 09:16:50.008916",
                "updated": "2025-07-21 09:16:50.008916",
                "monetary_account_id": 2428053,
                "amount": {
                    "currency": "EUR",
                    "value": "-0.10"
                },
                "payment_fee": null,
                "description": "test",
                "type": "BUNQ",
                "merchant_reference": null,
                "alias": {
                    "iban": "NL51BUNQ2093937468",
                    "is_light": false,
                    "display_name": "A. Visser",
                    "avatar": {
                        "uuid": "659e794e-f55e-4cfe-9f6d-465622cf43da",
                        "image": [
                            {
                                "attachment_public_uuid": "523b48a0-7a77-4598-9920-86f26135fef0",
                                "height": 1023,
                                "width": 1024,
                                "content_type": "image/png",
                                "urls": [
                                    {
                                        "type": "ORIGINAL",
                                        "url": "https://bunq-triage-model-storage-public.s3.eu-central-1.amazonaws.com/bunq_file/File/content/921ece497cd00f4e0cef3f0f63a962c31cf3f8e35311d127d5a7b23be3d074d5.png"
                                    }
                                ]
                            }
                        ],
                        "anchor_uuid": null,
                        "style": "NONE"
                    },
                    "label_user": {
                        "uuid": "e52b4912-c314-423a-80a4-939f4c992d14",
                        "display_name": "A. Visser",
                        "country": "NL",
                        "avatar": {
                            "uuid": "9debf55b-70b1-4d43-8d51-d6e7bd42fb5b",
                            "image": [
                                {
                                    "attachment_public_uuid": "4b7e0d1d-9167-48ac-990a-70e342c87812",
                                    "height": 126,
                                    "width": 200,
                                    "content_type": "image/jpeg",
                                    "urls": [
                                        {
                                            "type": "ORIGINAL",
                                            "url": "https://bunq-triage-model-storage-public.s3.eu-central-1.amazonaws.com/bunq_file/File/content/6979a145b7ea9ecc3459358122cb560608f02d36d4b8cd6b770f50e36aa35512.jpg"
                                        }
                                    ]
                                }
                            ],
                            "anchor_uuid": "e52b4912-c314-423a-80a4-939f4c992d14",
                            "style": "NONE"
                        },
                        "public_nick_name": "Ayako",
                        "type": "PERSON"
                    },
                    "country": "NL"
                },
                "counterparty_alias": {
                    "iban": "NL32BUNQ2025313705",
                    "is_light": false,
                    "display_name": "Sugar Daddy",
                    "avatar": {
                        "uuid": "3fada745-8305-45ab-b8c2-09cc05a51b65",
                        "image": [
                            {
                                "attachment_public_uuid": "23f17b08-9ece-4c05-8dd8-4caf2b6ef549",
                                "height": 1023,
                                "width": 1024,
                                "content_type": "image/png",
                                "urls": [
                                    {
                                        "type": "ORIGINAL",
                                        "url": "https://bunq-triage-model-storage-public.s3.eu-central-1.amazonaws.com/bunq_file/File/content/a3fa4840d155cc0f474258d10eb307f5c673eecf7f6d596a56e71c625cbd8cee.png"
                                    }
                                ]
                            }
                        ],
                        "anchor_uuid": null,
                        "style": "NONE"
                    },
                    "label_user": {
                        "uuid": "297f4374-38c5-4611-9708-71297ab56a4b",
                        "display_name": "Sugar Daddy",
                        "country": "NL",
                        "avatar": {
                            "uuid": "2971760d-ee2d-4bfb-9ea0-22cafefe85c3",
                            "image": [
                                {
                                    "attachment_public_uuid": "060a6f77-773f-4501-a071-f8b8093b9c72",
                                    "height": 480,
                                    "width": 480,
                                    "content_type": "image/jpeg",
                                    "urls": [
                                        {
                                            "type": "ORIGINAL",
                                            "url": "https://bunq-triage-model-storage-public.s3.eu-central-1.amazonaws.com/bunq_file/File/content/e68628d7e39aa9f764999abfd8fc9c0ed2667560ca05f56b5bff6e4a30e7e80c.jpg"
                                        }
                                    ]
                                }
                            ],
                            "anchor_uuid": "297f4374-38c5-4611-9708-71297ab56a4b",
                            "style": "NONE"
                        },
                        "public_nick_name": "Sugar Daddy",
                        "type": "PERSON"
                    },
                    "country": "NL"
                },
                "attachment": [],
                "geolocation": null,
                "batch_id": null,
                "scheduled_id": null,
                "address_billing": null,
                "address_shipping": null,
                "sub_type": "PAYMENT",
                "payment_arrival_expected": {
                    "status": "ARRIVED",
                    "time": null
                },
                "request_reference_split_the_bill": [],
                "balance_after_mutation": {
                    "currency": "EUR",
                    "value": "3.90"
                },
                "payment_auto_allocate_instance": null,
                "payment_suspended_outgoing": null
            }
        }
    ]
}
```


# RequestInquiry

Need to ask someone to pay you? That’s where **RequestInquiry** comes in.

With bunq, you can request money from anyone—bunq user or not—as long as you have their **IBAN**, **email**, or **phone number**. If they’re not a bunq user, they’ll get an email or SMS with a link to securely complete the payment from their own bank.

The `RequestInquiry` object handles this process. Like payments, it’s tied to a **MonetaryAccount**, and you can manage it through this endpoint:\
`POST /user/{userId}/monetary-account/{monetaryAccountId}/request-inquiry`

It’s a simple way to collect payments, whether you're splitting a bill, collecting rent, or invoicing a customer—no awkward follow-ups required.

## Example

```json
{
    "Response": [
        {
            "RequestInquiry": {
                "id": 2025724,
                "created": "2025-07-21 09:16:40.680832",
                "updated": "2025-07-21 09:16:43.715527",
                "time_responded": "2025-07-21 09:16:43.715420",
                "time_expiry": null,
                "monetary_account_id": 2428053,
                "amount_inquired": {
                    "currency": "EUR",
                    "value": "4.00"
                },
                "amount_responded": {
                    "currency": "EUR",
                    "value": "4.00"
                },
                "status": "ACCEPTED",
                "description": "4 chicken nuggets",
                "merchant_reference": null,
                "user_alias_created": {
                    "iban": "NL51BUNQ2093937468",
                    "is_light": false,
                    "display_name": "A. Visser",
                    "avatar": {
                        "uuid": "659e794e-f55e-4cfe-9f6d-465622cf43da",
                        "image": [
                            {
                                "attachment_public_uuid": "523b48a0-7a77-4598-9920-86f26135fef0",
                                "height": 1023,
                                "width": 1024,
                                "content_type": "image/png",
                                "urls": [
                                    {
                                        "type": "ORIGINAL",
                                        "url": "https://bunq-triage-model-storage-public.s3.eu-central-1.amazonaws.com/bunq_file/File/content/921ece497cd00f4e0cef3f0f63a962c31cf3f8e35311d127d5a7b23be3d074d5.png"
                                    }
                                ]
                            }
                        ],
                        "anchor_uuid": null,
                        "style": "NONE"
                    },
                    "label_user": {
                        "uuid": "e52b4912-c314-423a-80a4-939f4c992d14",
                        "display_name": "A. Visser",
                        "country": "NL",
                        "avatar": {
                            "uuid": "9debf55b-70b1-4d43-8d51-d6e7bd42fb5b",
                            "image": [
                                {
                                    "attachment_public_uuid": "4b7e0d1d-9167-48ac-990a-70e342c87812",
                                    "height": 126,
                                    "width": 200,
                                    "content_type": "image/jpeg",
                                    "urls": [
                                        {
                                            "type": "ORIGINAL",
                                            "url": "https://bunq-triage-model-storage-public.s3.eu-central-1.amazonaws.com/bunq_file/File/content/6979a145b7ea9ecc3459358122cb560608f02d36d4b8cd6b770f50e36aa35512.jpg"
                                        }
                                    ]
                                }
                            ],
                            "anchor_uuid": "e52b4912-c314-423a-80a4-939f4c992d14",
                            "style": "NONE"
                        },
                        "public_nick_name": "Ayako",
                        "type": "PERSON"
                    },
                    "country": "NL"
                },
                "user_alias_revoked": null,
                "counterparty_alias": {
                    "iban": "NL32BUNQ2025313705",
                    "is_light": false,
                    "display_name": "S. Daddy",
                    "avatar": {
                        "uuid": "3fada745-8305-45ab-b8c2-09cc05a51b65",
                        "image": [
                            {
                                "attachment_public_uuid": "23f17b08-9ece-4c05-8dd8-4caf2b6ef549",
                                "height": 1023,
                                "width": 1024,
                                "content_type": "image/png",
                                "urls": [
                                    {
                                        "type": "ORIGINAL",
                                        "url": "https://bunq-triage-model-storage-public.s3.eu-central-1.amazonaws.com/bunq_file/File/content/a3fa4840d155cc0f474258d10eb307f5c673eecf7f6d596a56e71c625cbd8cee.png"
                                    }
                                ]
                            }
                        ],
                        "anchor_uuid": null,
                        "style": "NONE"
                    },
                    "label_user": {
                        "uuid": "297f4374-38c5-4611-9708-71297ab56a4b",
                        "display_name": "Sugar Daddy",
                        "country": "NL",
                        "avatar": {
                            "uuid": "2971760d-ee2d-4bfb-9ea0-22cafefe85c3",
                            "image": [
                                {
                                    "attachment_public_uuid": "060a6f77-773f-4501-a071-f8b8093b9c72",
                                    "height": 480,
                                    "width": 480,
                                    "content_type": "image/jpeg",
                                    "urls": [
                                        {
                                            "type": "ORIGINAL",
                                            "url": "https://bunq-triage-model-storage-public.s3.eu-central-1.amazonaws.com/bunq_file/File/content/e68628d7e39aa9f764999abfd8fc9c0ed2667560ca05f56b5bff6e4a30e7e80c.jpg"
                                        }
                                    ]
                                }
                            ],
                            "anchor_uuid": "297f4374-38c5-4611-9708-71297ab56a4b",
                            "style": "NONE"
                        },
                        "public_nick_name": "Sugar Daddy",
                        "type": "PERSON"
                    },
                    "country": "NL"
                },
                "attachment": [],
                "minimum_age": null,
                "require_address": null,
                "geolocation": null,
                "bunqme_share_url": null,
                "redirect_url": null,
                "reference_split_the_bill": null,
                "batch_id": null,
                "scheduled_id": null,
                "address_billing": null,
                "address_shipping": null,
                "bizum_request": null
            }
        }
    ]
}
```


# Card

Cards are an essential part of the bunq experience—and just like everything else, each **Card** is directly linked to a **User**.

Every card must be connected to a **MonetaryAccount**, since that’s where the money comes from (and goes to). A single monetary account can be linked to multiple cards, but each card can only have **one primary account**. If there aren't enough funds in the primary account, bunq will check the **secondary account** (if set). If both are empty, the transaction will fail.

Cards also play a key role in **2-factor authentication**, using the user’s PIN to authorize payments. These card payments are stored as regular `Payment` records and can be viewed via:\
`GET /user/{userId}/monetary-account/{monetaryAccountId}/payment`

Want to see card-specific transaction details? You’ll need to query the `MasterCardAction` endpoint:\
`GET /user/{userId}/monetary-account/{monetaryAccountId}/mastercard-action`

## Example

```json
{
    "Response": [
        {
            "CardDebit": {
                "id": 475280,
                "created": "2025-07-21 09:21:53.715669",
                "updated": "2025-07-21 09:21:53.715669",
                "public_uuid": "c82f6de1-ff18-4f98-944e-d83e31b08c1f",
                "first_line": "",
                "second_line": "",
                "expiry_date": "2029-08-31",
                "status": "DEACTIVATED",
                "sub_status": "NONE",
                "order_status": "NEW_CARD_REQUEST_RECEIVED",
                "type": "MASTERCARD",
                "sub_type": "NONE",
                "product_type": "MASTERCARD_DEBIT",
                "product_sub_type": "NONE",
                "user_id": 2040503,
                "user_owner_id": 2040503,
                "user_holder_id": 2040503,
                "name_on_card": "A. Visser",
                "preferred_name_on_card": null,
                "card_limit": {
                    "currency": "EUR",
                    "value": "1000.00"
                },
                "card_limit_atm": {
                    "currency": "EUR",
                    "value": "1000.00"
                },
                "pin_code_assignment": [
                    {
                        "id": 476550,
                        "created": "2025-07-21 09:21:57.166688",
                        "updated": "2025-07-21 09:21:57.166688",
                        "type": "PRIMARY",
                        "monetary_account_id": 2428053,
                        "status": "ACTIVE",
                        "routing_type": "AUTOMATIC"
                    }
                ],
                "country": "NL",
                "country_permission": [
                    {
                        "country": "AT"
                    },
                    {
                        "country": "BE"
                    },
                    {
                        "country": "BG"
                    },
                    {
                        "country": "HR"
                    },
                    {
                        "country": "CY"
                    },
                    {
                        "country": "CZ"
                    },
                    {
                        "country": "DK"
                    },
                    {
                        "country": "EE"
                    },
                    {
                        "country": "FI"
                    },
                    {
                        "country": "GF"
                    },
                    {
                        "country": "FR"
                    },
                    {
                        "country": "DE"
                    },
                    {
                        "country": "GI"
                    },
                    {
                        "country": "GR"
                    },
                    {
                        "country": "GP"
                    },
                    {
                        "country": "HU"
                    },
                    {
                        "country": "IE"
                    },
                    {
                        "country": "IT"
                    },
                    {
                        "country": "LV"
                    },
                    {
                        "country": "LT"
                    },
                    {
                        "country": "LU"
                    },
                    {
                        "country": "MT"
                    },
                    {
                        "country": "NL"
                    },
                    {
                        "country": "PL"
                    },
                    {
                        "country": "PT"
                    },
                    {
                        "country": "RO"
                    },
                    {
                        "country": "SK"
                    },
                    {
                        "country": "SI"
                    },
                    {
                        "country": "ES"
                    },
                    {
                        "country": "SE"
                    },
                    {
                        "country": "AD"
                    },
                    {
                        "country": "AL"
                    },
                    {
                        "country": "AM"
                    },
                    {
                        "country": "AZ"
                    },
                    {
                        "country": "BA"
                    },
                    {
                        "country": "BY"
                    },
                    {
                        "country": "CH"
                    },
                    {
                        "country": "GB"
                    },
                    {
                        "country": "GE"
                    },
                    {
                        "country": "IS"
                    },
                    {
                        "country": "LI"
                    },
                    {
                        "country": "MC"
                    },
                    {
                        "country": "MD"
                    },
                    {
                        "country": "ME"
                    },
                    {
                        "country": "MK"
                    },
                    {
                        "country": "NO"
                    },
                    {
                        "country": "RS"
                    },
                    {
                        "country": "SM"
                    },
                    {
                        "country": "UA"
                    },
                    {
                        "country": "US"
                    },
                    {
                        "country": "VA"
                    }
                ],
                "label_monetary_account_ordered": {
                    "iban": "NL51BUNQ2093937468",
                    "is_light": false,
                    "display_name": "A. Visser",
                    "avatar": {
                        "uuid": "659e794e-f55e-4cfe-9f6d-465622cf43da",
                        "image": [
                            {
                                "attachment_public_uuid": "523b48a0-7a77-4598-9920-86f26135fef0",
                                "height": 1023,
                                "width": 1024,
                                "content_type": "image/png",
                                "urls": [
                                    {
                                        "type": "ORIGINAL",
                                        "url": "https://bunq-triage-model-storage-public.s3.eu-central-1.amazonaws.com/bunq_file/File/content/921ece497cd00f4e0cef3f0f63a962c31cf3f8e35311d127d5a7b23be3d074d5.png"
                                    }
                                ]
                            }
                        ],
                        "anchor_uuid": null,
                        "style": "NONE"
                    },
                    "label_user": {
                        "uuid": "e52b4912-c314-423a-80a4-939f4c992d14",
                        "display_name": "A. Visser",
                        "country": "NL",
                        "avatar": {
                            "uuid": "9debf55b-70b1-4d43-8d51-d6e7bd42fb5b",
                            "image": [
                                {
                                    "attachment_public_uuid": "4b7e0d1d-9167-48ac-990a-70e342c87812",
                                    "height": 126,
                                    "width": 200,
                                    "content_type": "image/jpeg",
                                    "urls": [
                                        {
                                            "type": "ORIGINAL",
                                            "url": "https://bunq-triage-model-storage-public.s3.eu-central-1.amazonaws.com/bunq_file/File/content/6979a145b7ea9ecc3459358122cb560608f02d36d4b8cd6b770f50e36aa35512.jpg"
                                        }
                                    ]
                                }
                            ],
                            "anchor_uuid": "e52b4912-c314-423a-80a4-939f4c992d14",
                            "style": "NONE"
                        },
                        "public_nick_name": "Ayako",
                        "type": "PERSON"
                    },
                    "country": "NL"
                },
                "label_monetary_account_current": {
                    "iban": "NL51BUNQ2093937468",
                    "is_light": false,
                    "display_name": "A. Visser",
                    "avatar": {
                        "uuid": "659e794e-f55e-4cfe-9f6d-465622cf43da",
                        "image": [
                            {
                                "attachment_public_uuid": "523b48a0-7a77-4598-9920-86f26135fef0",
                                "height": 1023,
                                "width": 1024,
                                "content_type": "image/png",
                                "urls": [
                                    {
                                        "type": "ORIGINAL",
                                        "url": "https://bunq-triage-model-storage-public.s3.eu-central-1.amazonaws.com/bunq_file/File/content/921ece497cd00f4e0cef3f0f63a962c31cf3f8e35311d127d5a7b23be3d074d5.png"
                                    }
                                ]
                            }
                        ],
                        "anchor_uuid": null,
                        "style": "NONE"
                    },
                    "label_user": {
                        "uuid": "e52b4912-c314-423a-80a4-939f4c992d14",
                        "display_name": "A. Visser",
                        "country": "NL",
                        "avatar": {
                            "uuid": "9debf55b-70b1-4d43-8d51-d6e7bd42fb5b",
                            "image": [
                                {
                                    "attachment_public_uuid": "4b7e0d1d-9167-48ac-990a-70e342c87812",
                                    "height": 126,
                                    "width": 200,
                                    "content_type": "image/jpeg",
                                    "urls": [
                                        {
                                            "type": "ORIGINAL",
                                            "url": "https://bunq-triage-model-storage-public.s3.eu-central-1.amazonaws.com/bunq_file/File/content/6979a145b7ea9ecc3459358122cb560608f02d36d4b8cd6b770f50e36aa35512.jpg"
                                        }
                                    ]
                                }
                            ],
                            "anchor_uuid": "e52b4912-c314-423a-80a4-939f4c992d14",
                            "style": "NONE"
                        },
                        "public_nick_name": "Ayako",
                        "type": "PERSON"
                    },
                    "country": "NL"
                },
                "monetary_account": null,
                "primary_account_numbers": [
                    {
                        "id": 478304,
                        "uuid": "078374fd-6a2b-4978-b291-1e246142b45d",
                        "description": "",
                        "four_digit": "6584",
                        "type": "CARD_MASTERCARD_DEBIT"
                    }
                ],
                "payment_account_reference": null,
                "monetary_account_id_fallback": null,
                "card_shipment_tracking_url": null,
                "is_card_eligible_for_free_replacement": true,
                "card_replacement": null,
                "is_limited_edition": false,
                "card_metal_member_since_date": null,
                "company_employee_card": null,
                "card_generated_cvc2": {
                    "CardGeneratedCvc2": {
                        "id": 373278,
                        "created": "2025-07-21 09:21:57.556343",
                        "updated": "2025-07-21 09:21:57.556343",
                        "cvc2": "582",
                        "status": "AVAILABLE",
                        "expiry_time": null,
                        "type": "STATIC"
                    }
                }
            }
        }
    ]
}
```


# Attachment and Note Attachment

Sometimes, a payment or request needs a little extra context—like an invoice, receipt, or a photo. That’s where **attachments** come in.

You can attach `.png` or `.jpeg` images to both **Payment** and **RequestInquiry** objects, either before or after they’re created:

* Use an **Attachment** when you want to upload a file first and then link it while creating a payment or request.
* Use a **NoteAttachment** when you want to add a file to an existing object (like a sent payment or draft).

### **🧩 What's the difference?**

* **Attachment**:\
  A general file upload linked directly to the **MonetaryAccount**.\
  Endpoint:\
  `POST /user/{userId}/monetary-account/{monetaryAccountId}/attachment`
* **NoteAttachment**:\
  A file added to a specific object (like a `Payment`, `RequestInquiry`, etc.) after it's been created.\
  Endpoints:
  * `/user/{userId}/monetary-account/{monetaryAccountId}/payment/{paymentId}/note-attachment`
  * `/user/{userId}/monetary-account/{monetaryAccountId}/request-inquiry/{requestInquiryId}/note-attachment`
  * `/user/{userId}/monetary-account/{monetaryAccountId}/draft-payment/{draftPaymentId}/note-attachment`
  * `/user/{userId}/monetary-account/{monetaryAccountId}/schedule-payment/{schedulePaymentId}/note-attachment`

This setup gives you flexibility—whether you're attaching a receipt during a payment, or adding an invoice afterward.


# Callbacks (Webhooks)

Looking for Webhooks? You're on the right page!

Callbacks are used to send real-time notifications on the events that happen on a bunq account. To receive notifications for certain events on a bunq account, you need to create notification filters. It is possible to send the notifications to a provided URL and/or the user's phone as push notifications.

## Notification Filters

Use the `notification-filter-push` resource to create and manage push notification filters. Provide the type of events you want to receive notifications about in the `category` field.

Example request body:

```json
{
   "notification_filters":[
      {
         "category":"SCHEDULE_RESULT"
      }
   ]
}
```

Use the `notification-filter-url` resource to create and manage URL notification filters. The callback URL you provide in the `notification_target` field must use HTTPS.

Example request body:

```json
{
   "notification_filters":[
      {
         "category":"PAYMENT",
         "notification_target":"{YOUR_CALLBACK_URL}"
      }
   ]
}
```

## Callback categories

The `category` field determines which events trigger a callback. Categories are grouped below by domain.

### Payments & mutations

| Category            | Description                                                                                                                                                       |
| ------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `MUTATION`          | Any change in the balance of a monetary account. Fired for all payment-like objects (payments, requests, iDEAL, etc.). Use this to track account balance changes. |
| `PAYMENT`           | Payment created or received on a bunq account                                                                                                                     |
| `INSTANT_PAYMENT`   | Instant payment (SCT Inst) created or received                                                                                                                    |
| `PAYMENT_DELAYED`   | Payment is delayed and not yet processed                                                                                                                          |
| `PAYMENT_SUSPENDED` | Payment has been suspended                                                                                                                                        |
| `PAYMENT_UPDATED`   | Payment status has been updated                                                                                                                                   |
| `DRAFT_PAYMENT`     | Draft payment created or updated                                                                                                                                  |
| `REQUEST`           | Incoming payment request, or update on an outgoing request                                                                                                        |
| `SCHEDULE_RESULT`   | Scheduled payment has been executed                                                                                                                               |
| `SCHEDULE_STATUS`   | Scheduled payment status update or cancellation                                                                                                                   |
| `IDEAL`             | iDEAL deposit towards a bunq account                                                                                                                              |
| `SOFORT`            | SOFORT deposit towards a bunq account                                                                                                                             |

### Cards

| Category                                   | Description                                                               |
| ------------------------------------------ | ------------------------------------------------------------------------- |
| `CARD_TRANSACTION_SUCCESSFUL`              | Successful card transaction                                               |
| `CARD_TRANSACTION_FAILED`                  | Failed card transaction                                                   |
| `CARD_TRANSACTION_UPDATED`                 | Card transaction updated (e.g. amount change after pre-authorization)     |
| `CARD_TRANSACTION_REFUND`                  | Card transaction refunded                                                 |
| `CARD_TRANSACTION_TOKENIZED`               | Card transaction made via a digital wallet token (Apple Pay / Google Pay) |
| `CARD_DYNAMIC_CURRENCY_CONVERSION_WARNING` | Dynamic currency conversion (DCC) warning issued before authorization     |
| `CARD_EXPIRY`                              | Card is nearing or has reached its expiry date                            |
| `CARD_REPLACEMENT`                         | Card replacement has been initiated                                       |
| `CARD_COUNTRY_PERMISSION`                  | Card country permissions have changed                                     |
| `CARD_TOKENIZATION`                        | Card tokenization event (token created or updated)                        |
| `MASTERCARD_IDENTITY_CHECK`                | Mastercard Identity Check (3D Secure) authentication event                |

### Monetary accounts

| Category                            | Description                                       |
| ----------------------------------- | ------------------------------------------------- |
| `MONETARY_ACCOUNT_BALANCE`          | Account balance crossed a configured threshold    |
| `MONETARY_ACCOUNT_JOINT`            | Joint account event (e.g. created or updated)     |
| `CO_OWNER_INVITE`                   | Co-owner invitation sent or received              |
| `APPROVAL_REQUEST_USER`             | User-level approval request created or updated    |
| `APPROVAL_REQUEST_MONETARY_ACCOUNT` | Account-level approval request created or updated |

### Auto-payments (Whitelist)

| Category           | Description                                     |
| ------------------ | ----------------------------------------------- |
| `WHITELIST`        | Auto-payment whitelist entry created or updated |
| `WHITELIST_RESULT` | Auto-payment whitelist rule executed            |

### Tabs & bunq.me

| Category                     | Description                                            |
| ---------------------------- | ------------------------------------------------------ |
| `TAB_RESULT`                 | Tab payment received or updated                        |
| `BUNQME_TAB`                 | bunq.me Tab (open request) payment received or updated |
| `BUNQME_TAB_RESULT_INQUIRY`  | bunq.me Tab result inquiry                             |
| `BUNQME_TAB_RESULT_RESPONSE` | bunq.me Tab result response                            |

### OAuth & security

| Category | Description              |
| -------- | ------------------------ |
| `OAUTH`  | OAuth connection revoked |

## Receiving Callbacks

Callbacks for the sandbox environment will be made from different IP's at AWS.

Callbacks for the production environment will be made from `185.40.108.0/22`.

> The IP addresses might change. We will notify you in a timely fashion if such a change is planned.

## Removing callbacks

To remove callbacks for an object, send a POST request to the `notification_filters` endpoint with a JSON request body with an empty list.

```json
{
    "notification_filters": []
}
```

## Retry Mechanisms

When the execution of a callback fails (e.g. the callback server is down or the response contains an error), we try to resend it for a maximum of 5 times, with an interval of one minute between each try. If your server is not reachable by the callback after the 6th total try, the callback is not sent anymore.

## Listing of failed callbacks

After the sixth attempt of callback executing, the failed entry is stored and can be listed by UserApiKey.

```
GET /v1/user/762/notification-filter-failure
```

```json
{
    "Response": [
        {
            "NotificationFilterFailure": {
                "id": 1,
                "created": "2023-05-22 06:47:22.043906",
                "updated": "2023-05-22 06:47:22.043906",
                "category": "MUTATION",
                "event_type": "MUTATION_CREATED",
                "object_id": 1278,
                "notification_filters": [
                    {
                        "notification_delivery_method": "URL",
                        "notification_target": "https://coolbank.com/notification",
                        "category": "MUTATION"
                    },
                    {
                        "notification_delivery_method": "URL",
                        "notification_target": "https://coolbank.com/notification",
                        "category": "CARD_TRANSACTION_SUCCESSFUL"
                    }
                ]
            }
        }
    ],
    "Pagination": {
        "future_url": "/v1/user/762/notification-filter-failure?newer_id=1",
        "newer_url": null,
        "older_url": null
    }
}
```

* The `category` and `object_id` can be used to verify if the callback has failed and should be retried.
* The `id` of the `NotificationFilterFailure` object should be used to trigger the retry.

## Retry of failed callbacks

```
POST /v1/user/762/notification-filter-failure
```

```json
{
    "notification_filter_failed_ids": "1"
}
```

* Multiple ids can be given in the same field, comma separated. Maximum of 100 ids are allowed.
* Response will be empty with code 200 (OK).

## Setting up a callback

Check the callback categories above for all possible callbacks. Here we show one example API call to set up a new URL notification filter for successful card transactions:

```json
{
    "notification_filters": [
        {
            "category": "CARD_TRANSACTION_SUCCESSFUL",
            "notification_target": "https://webhook.site/994966bb-7a4c-4be3-836a-da65231b907d"
        }
    ]
}
```

## Certificate Pinning

We recommend that you use certificate pinning as an extra security measure. We will check if the certificate of the recipient server matches the pinned certificate that you provided and cancel the callback if the check fails or we detect a mismatch.

### How to set up certificate pinning

Retrieve the SSL certificate of your server using the following command:

```bash
openssl s_client -servername www.example.com -connect www.example.com:443 < /dev/null | sed -n "/-----BEGIN/,/-----END/p" > www.example.com.pem
```

POST the certificate to the `certificate-pinned` endpoint.

Once ready, every callback will be checked against the pinned certificate that you provided. Note that if the SSL certificate on your server expires or is changed, our callbacks will fail.


# Errors

We use the HTTP response codes to indicate the success or failure of an API request:

* codes in the 2xx range indicate success;
* codes in the 4xx range indicate an error having to do with the provided information (e.g. a required parameter was missing, insufficient funds, etc.);
* codes in the 5xx range indicate an error having to do with the bunq servers. If you get such an error, please report it to us via chat.

## Response Codes

| Code | Error                      | Description                                                       |
| ---- | -------------------------- | ----------------------------------------------------------------- |
| 200  | OK                         | Successful HTTP request                                           |
| 399  | NOT MODIFIED               | Same as 304. It implies you have a local cached copy of the data. |
| 400  | BAD REQUEST                | A parameter is missing or invalid.                                |
| 401  | UNAUTHORISED               | A token or signature provided is not valid.                       |
| 403  | FORBIDDEN                  | You're not allowed to make this call.                             |
| 404  | NOT FOUND                  | The object you're looking for cannot be found.                    |
| 405  | METHOD NOT ALLOWED         | The method you are using is not allowed for this endpoint.        |
| 429  | RATE LIMIT                 | Too many API calls have been made in a too short period of time.  |
| 466  | REQUEST SIGNATURE REQUIRED | Request signature is required for this operation.                 |
| 490  | USER ERROR                 | A parameter is missing or invalid.                                |
| 491  | MAINTENANCE ERROR          | bunq is in maintenance mode.                                      |
| 500  | INTERNAL SERVER ERROR      | Something went wrong on our end.                                  |

{% hint style="info" %}
All 4xx errors include a JSON body explaining what went wrong.
{% endhint %}

## Rate Limits

If you are receiving the 429 error, please make sure you are sending requests at rates that are below our rate limits.

Here are our **rate limits per IP address per endpoint:**

* **GET requests:** 3 requests within any 3 consecutive seconds
* **POST requests:** 5 requests within any 3 consecutive seconds
* **PUT requests:** 2 requests within any 3 consecutive seconds
* **Callbacks:** 2 callback URLs per notification category


# Geolocations

### Overview

The bunq API allows you to attach a geolocation to a payment using the `X-Bunq-Geolocation` request header. This metadata can then be queried later via payment-related endpoints.

### Adding Geolocation to a Payment

To include geolocation data with a payment, set the `X-Bunq-Geolocation` header when making a payment request.

#### Header format

```
X-Bunq-Geolocation: {latitude} {longitude} {altitude} {radius} {country}
```

**Example:**

```
X-Bunq-Geolocation: 52.0907 5.1214 0 50 NL
```

| Field     | Type   | Description                         |
| --------- | ------ | ----------------------------------- |
| latitude  | float  | Latitude in decimal degrees         |
| longitude | float  | Longitude in decimal degrees        |
| altitude  | float  | Altitude in meters (can be `0`)     |
| radius    | float  | Accuracy radius in meters           |
| country   | string | 2-letter ISO country code (e.g. NL) |

### Example: Creating a Payment with Geolocation

```http
POST /v1/user/{userID}/monetary-account/{monetaryAccountID}/payment

Headers:
X-Bunq-Geolocation: 52.0907 5.1214 0 50 NL


Body:
{
  "amount": {
    "currency": "EUR",
    "value": "10.00"
  },
  "counterparty_alias": {
    "type": "EMAIL",
    "value": "john@doe.com"
  },
  "description": "Lunch"
}
```

***

### Retrieving Payments with Geolocation

Geolocation is attached to:

* `/payment`
* `/draft-payment`
* `/mastercard-action`

To fetch this metadata:

```http
GET /v1/user/{userID}/monetary-account/{monetaryAccountID}/payment/{paymentID}
```

#### Response (excerpt)

[Payment](/basics/bunq-api-objects/payment)s that do have a location will have a geolocation property filled like so:

```json
{
  "payment": {
    "description": "Lunch",
    ...
    "geolocation": {
      "latitude": 52.3676,
      "longitude": 4.9041,
      "altitude": 0,
      "radius": 30
    }
  }
}
```

> 🧠 Only payments made with the geolocation header will include this metadata.

***

### Limitations

* There is **no API** to retrieve the **current geolocation** of a user.
* There is no geolocation API. You'll have to collect all payments and filter in your own implementation for payments and locations you're interested in
* Geolocation is only stored **at the time of certain actions** (e.g. making a payment). bunq only adds the geolocation to payments where it is available. So account for the field missing even on payments made by bunq.


# Headers

HTTP headers allow your application and bunq to pass additional information about with the request or response.

Though headers are already implemented in our [SDKs](broken://pages/ZOBhBQTwIMvx9EpWwDg6), we recommend that you follow these instructions to make sure you set appropriate headers when calling the bunq API directly.

## Request Headers

### Mandatory request headers

**Cache-Control**

Example:

`Cache-Control: no-cache`

The standard HTTP Cache-Control header is required for all requests.

**User-Agent**

Example:

`User-Agent: bunq-TestServer/1.00 sandbox/0.17b3`

The User-Agent header field must contain information about the user agent originating the request. There are no restrictions on the value of this header.

**X-Bunq-Client-Signature**

Since April 28 2020 we stopped validating the Header and URL signatures. Currently we only require signatures on API calls that create Payments or start a new session. For these API calls you only need to [sign the request body](/basics/signing)

Example:

```
X-Bunq-Client-Signature: 
XLOwEdyjF1d+tT2w7a7Epv4Yj7w74KncvVfq9mDJVvFRlsUaMLR2q4ISgT+5mkwQsSygRRbooxBqydw7IkqpuJay9g8eOngsFyIxSgf2vXGAQatLm47tLoUFGSQsRiYoKiTKkgBwA+/3dIpbDWd+Z7LEYVbHaHRKkEY9TJ22PpDlVgLLVaf2KGRiZ+9/+0OUsiiF1Fkd9aukv0iWT6N2n1P0qxpjW0aw8mC1nBSJuuk5yKtDCyQpqNyDQSOpQ8V56LNWM4Px5l6SQMzT8r6zk5DvrMAB9DlcRdUDcp/U9cg9kACXIgfquef3s7R8uyOWfKLSNBQpdVIpzljwNKI1Q
```

**X-Bunq-Client-Authentication**

`X-Bunq-Client-Authentication: 622749ac8b00c81719ad0c7d822d3552e8ff153e3447eabed1a6713993749440`

The authentication *Token* is used to identify the sender of the API call. It is required for all API calls except `POST /v1/installation`.

* Pass the **installation&#x20;*****Token*** you get in the response to the `POST /installation` call in the `/device-server` and `/session-server` calls.
* Pass the **session&#x20;*****Token*** you get in the response to the `POST /session-server` call in all the other calls.

#### **Optional** request headers <a href="#optional-request-headers" id="optional-request-headers"></a>

**X-Bunq-Language**

`X-Bunq-Language: en_US`

`en_US` is the default language setting for responses and error descriptions.

The X-Bunq-Language header must carry the preferred language indicator. The value of this header must follow this format: *an ISO 639-1 language code* plus *a ISO 3166-1 alpha-2 country code* separated by an underscore.

We currently only support *en\_US* and *nl\_NL*. Any other language will default to *en\_US*.

**X-Bunq-Region**

`X-Bunq-Region: en_US`

`en_US` is the default region for localization formatting.

The X-Bunq-Region header must contain the region (country) of the client device. The value of this header must follow this format: *an ISO 639-1 language code* plus *a ISO 3166-1 alpha-2 country code* separated by an underscore.

**X-Bunq-Client-Request-Id**

`X-Bunq-Client-Request-Id: a4f0de`

This header has to specify an ID with each request that is unique for the logged in user. There are no restrictions for the format of this ID. However, the server will respond with an error when the same ID is used again on the same DeviceServer.

**X-Bunq-Geolocation**

`X-Bunq-Geolocation: 4.89 53.2 12 100 NL`

`X-Bunq-Geolocation: 0 0 0 0 000` *(if no geolocation is available or known)*

This header has to specify the geolocation of the device. The format of this value is *longitude latitude altitude radius country*. The country is expected to take the form of an *ISO 3166-1 alpha-2 country code*.

This header makes it possible for bunq to map the geolocation with the payment.

#### Attachment headers <a href="#attachment-headers" id="attachment-headers"></a>

**Content-Type**

`Content-Type: image/jpeg`

Use this header when uploading an attachment to pass its MIME type. We support the following content types:

* *image/png;*
* *image/jpeg;*
* *image/gif.*

**X-Bunq-Attachment-Description**

Use this header to provide a description of an attachment.

### Response Headers <a href="#response-headers" id="response-headers"></a>

#### All Responses <a href="#all-responses" id="all-responses"></a>

**X-Bunq-Client-Request-Id**

`X-Bunq-Client-Request-Id: a4f0de`

The header contains the same `id` that was provided in the `X-Bunq-Client-Request-Id` header of the request. It is included in the response (and request) signature so it can be used to ensure this is the response to *the* request.

**X-Bunq-Client-Response-Id**

`X-Bunq-Client-Response-Id: 76cc7772-4b23-420a-9586-8721dcdde174`

The header carries a unique `id` of the response formatted as a `UUID`. You can use it to add extra protection against replay attacks.

**X-Bunq-Server-Signature**

Copy

```
X-Bunq-Server-Signature: 
XBBwfDaOZJapvcBpAIBT1UOmczKqJXLSpX9ZWHsqXwrf1p+H+eON+TktYksAbmkSkI4gQghw1AUQSJh5i2c4+CTuKdZ4YuFT0suYG4sltiKnmtwODOFtu1IBGuE5XcfGEDDSFC+zqxypMi9gmTqjl1KI3WP2gnySRD6PBJCXfDxJnXwjRkk4kpG8Ng9nyxJiFG9vcHNrtRBj9ZXNdUAjxXZZFmtdhmJGDahGn2bIBWsCEudW3rBefycL1DlpJZw6yRLoDltxeBo7MjgROBpIeElh5qAz9vxUFLqIQC7EDONBGbSBjaXS0wWrq9s2MGuOi9kJxL2LQm/Olj2g==
```

The header contains the signature of the bunq server for this response. See the signing page for details on how to verify this signature.


# Moving to production

Have you tested your bunq integration to the fullest and are now ready to introduce your application to the world? It's time to move it to the production environment!

Here is what you need to do to get started:

1. Generate a production API key via the bunq app. Go *Profile → Security & Settings → Developers → API keys*.
2. Change your API Key and redo the [sequence of calls to open a session](/tutorials/your-first-payment/creating-the-api-context/start-a-session).
3. Change the base url to: <https://api.bunq.com>.

We highly recommend using a standard production API Key instead of a [Wildcard API Key](https://together.bunq.com/d/1997-the-new-wildcard-api-key). The former is significantly safer and it protects you from malicious intrusions and attacks.

{% hint style="warning" %}
Please be aware that if you will gain access to account information of other bunq users or initiate a payment for them, you may be legally required to first obtain a PSD2 permit. More information on this can be found [here](/psd2/are-you-a-third-party-provider-tpp-start-here)
{% endhint %}


# Pagination

To control the size of the response of a list request, items can be paginated.

A **list request** is a request that is meant to retrieve a large number of items such as all payments of a certain monetary account `GET /v1/user/1/monetary-account/1/payment`. You can choose the maximum amount of items to be included in the response by adding a `count` query parameter with the number of items you want per page to the URL.

**Example:** `GET /v1/user/1/monetary-account/1/payment?count=25`

When no `count` is given, the default count is set to 10. The maximum `count` you can set is 200.

With every listing, a `Pagination` object will be added to the response. It will contain the URLs you need to use to get the next or previous set of items. You can also use these URLs to navigate through the listed resources.

Here is what a `Pagination` object looks like:

Copy

```
{
    "Pagination": {
        "future_url": null,
        "newer_url": "/v1/user/1/monetary-account/1/payment?count=25&newer_id=249",
        "older_url": "/v1/user/1/monetary-account/1/payment?count=25&older_id=224"
    }
}
```

The `newer_url` value can be used to get the next page.

* The `newer_id` is always the `id` of the last item in the current page.
* If `newer_url` is `null`, there are no more items to be listed on the next page. The next page thus does not exist.

The `older_url` value can be used to get the previous page.

* The `older_id` is always the `id` of the first item in the current page.
* If `older_url` is `null`, there are no items on the previous page. The previous page thus does not exist.

The `future_url` can be used to refresh the list and check for new items that didn’t exist when the listing was requested. The `future_url` will be `null` if the `newer_id` is already the ID of the latest item.

### Pagination Behavior Explained

When you request a paginated list of payments, the response includes a `Pagination` object with up to three navigation URLs:

| Key          | Purpose                                                            | Example URL                                                  |
| ------------ | ------------------------------------------------------------------ | ------------------------------------------------------------ |
| `newer_url`  | Fetches the next (more recent) page of payments                    | `null` means you're already viewing the most recent payments |
| `older_url`  | Fetches the previous (older) page of payments                      | `/payment?count=3&older_id=118`                              |
| `future_url` | Lets you refresh the list to check if new payments have been added | `/payment?count=3&newer_id=120`                              |

***

**Example**

Suppose you call:

```
GET /v1/user/1/monetary-account/1/payment?count=3
```

You receive:

```json
{
  "payments": [... 3 payment objects],
  "Pagination": {
    "future_url": "/v1/user/1/monetary-account/1/payment?count=3&newer_id=120",
    "newer_url": null,
    "older_url": "/v1/user/1/monetary-account/1/payment?count=3&older_id=118"
  }
}
```

**What this tells you:**

* `newer_url: null` → You're already seeing the **most recent** payments.
* `older_url` exists → There are **older payments** available. Follow this URL to go back in time.
* `future_url` exists → You can use this to **check later** if new payments were added **after ID 120**.

If you later follow the `future_url` and no new payments exist, it will return:

```json
"Pagination": {
  "future_url": null
}
```

This confirms your list is **up to date**.


# Querying Payments

The bunq API provides flexible ways to access payment data. You can either list multiple payments with pagination or retrieve detailed information for a specific payment by its ID. Understanding how to best leverage these endpoints will help you build efficient, scalable integrations with bunq.

***

### Listing Payments with Pagination

When you want to retrieve multiple payments, the bunq API lets you list paymentsbelonging to a specific monetary account. This is ideal when you need to display recent transactions, build transaction histories, or analyze payments over time. Refer to the [Payment](/payment) documentation

To manage potentially large datasets, the API supports [Pagination](/basics/pagination). This means you specify how many payments you want per request, and the API provides navigation URLs to move through the list. Pagination parameters like `count`, `older_id`, and `newer_id` allow you to control the page size and direction.

Each payment in the list may or may not include geolocation metadata, depending on whether the payment was created with the geolocation header. Since the API doesn’t currently support querying or filtering payments by geolocation directly, you will need to filter these client-side after retrieving the payments.

The response includes a [Pagination](/basics/pagination) object with URLs to fetch the next or previous pages, helping you navigate the data without manually building query strings.

***

### Retrieving a Specific Payment

Sometimes you need detailed information on a single payment, for example to display full transaction details or to verify specific metadata like geolocation.

The API provides an endpoint to fetch a payment by its unique ID. This request returns all associated data including descriptions, amounts, and if available, the geolocation data provided at the time of payment creation. If you have a payment ID you can simply query the [Payment](/payment) API endpoint with the ID to get it's details.

Since not all payments contain geolocation data, you should always check for the presence of the geolocation property and handle its absence gracefully.

### Keeping track of users payments

To track a user’s payments efficiently, set up [Callbacks (Webhooks)](/basics/callbacks-webhooks) on their monetary account to receive real-time updates whenever a new payment is created or updated. When a webhook event arrives, fetch the full payment details using the payment ID and store or update it in your system. This ensures your data stays fresh without polling the API.

As a fallback or for historical data, use the [Pagination](/basics/pagination) payments endpoint to fetch recent payments. Store the latest known payment ID and use `newer_id` to retrieve only new entries. Always deduplicate using `payment.id`, and handle optional fields like `geolocation` or `attachment` gracefully, as not all payments will include them.

{% hint style="success" %}
To check if newer payments have been created since a specific payment ID, you can use the `future_url` provided in the [Pagination](/basics/pagination) response. If the `future_url` is not `null`, following it will return any newer payments that were added after that ID.
{% endhint %}

***

### Recommended Practices for Querying Payments

* **Efficient Pagination:** Always [Pagination](/basics/pagination) when listing payments. It helps manage large datasets, reduces memory usage, and prevents hitting rate limits.
* **Use Pagination URLs:** Instead of manually constructing queries with `older_id` or `newer_id`, use the `older_url` and `newer_url` returned by the API for smoother navigation.
* **Leverage Webhooks for Real-Time Updates:** Use bunq’s [Callbacks (Webhooks)](/basics/callbacks-webhooks)to receive notifications when a new payment is created or updated. This reduces the need for polling and helps you keep your local data in sync.
* **Client-Side Filtering:** Since the bunq API does not currently support filtering on fields like description, amount, or custom metadata, you’ll need to implement filtering in your own application after retrieving the payments. For example, you can filter payments by:
  * Date range
  * Minimum or maximum amount
  * Counterparty alias (e.g., a specific email or phone number)
  * Specific keywords in the description
  * Presence of optional fields like `geolocation`
* **Design for Optional Fields:** Not all payments include all fields (e.g., `geolocation`, `attachments`). Always check for presence before relying on them in your UI or processing logic.
* **Cache Frequently Used Data:** If your app or service frequently queries the same payments, consider caching the results to reduce API usage and improve speed.
* **Respect Rate Limits:** bunq enforces [Rate Limits](/basics/rate-limits) to ensure fair use. Implement retry logic with exponential backoff to gracefully handle any `429 Too Many Requests` responses.

***


# Rate Limits

If you receive a `429 RATE_LIMIT` error, you have exceeded the allowed number of requests for a given endpoint. bunq enforces **per-endpoint rate limits** — the window and maximum request count differ depending on what the endpoint does and how sensitive it is.

## How rate limits work

Every request is tracked per device (identified by your API key, session, or IP address). The system counts requests to a specific endpoint within a rolling time window. When you exceed the limit you receive an HTTP `429` response with a message indicating the exact limit:

> *"Too many requests. You can do a maximum of X calls per Y seconds to this endpoint."*

## Rate limits by endpoint category

| Endpoint category              | Example endpoints               | Limit                         |
| ------------------------------ | ------------------------------- | ----------------------------- |
| Payment processing             | Card lookups                    | up to 1,000 requests / second |
| Standard data endpoints        | User info, account info, events | 70–140 requests / 30 seconds  |
| Security-sensitive actions     | Alias registration              | 3 requests / 60 seconds       |
| Setup / installation endpoints | Device installation             | as low as 10 requests / day   |

{% hint style="info" %}
The exact limit for an endpoint is returned in the `429` error message itself. Build your retry logic around that value rather than assuming a fixed global limit.
{% endhint %}

## Callback limits

A maximum of **2 callback URLs** per notification category is allowed.

## Handling 429 errors

* Implement client-side rate limiting to stay below the per-endpoint limits.
* When you receive a `429`, back off and retry using **exponential backoff** (see [Querying Payments](https://doc.bunq.com/basics/querying-payments)).
* Do not hammer the endpoint — repeated 429s will extend your backoff window.


# Response body formatting

All JSON responses have one top level object. This object contains a Response field the value of which is always an array. This also applies to responses that contain only one object.

**Example** **response** **body structure:**

Copy

```
{
    "Response": [
        {
            "DataObject": {}
        }
    ]
}
```

#### Errors <a href="#errors" id="errors"></a>

* Error responses also have one top level Error object.
* The contents of the array is a JSON object with the *error\_description* and *error\_description\_translated* fields.
* The *error\_description* field contains the error explanation in the English language
* The *error\_description\_translated* field can be shown to the end users. It is automatically translated into the language specified in the `X-Bunq-Language` header. The default language is en\_US.
* If you are using one of the bunq SDKs, error responses will be always raised in form of an exception.

**Example response body:**

Copy

```
{
    "Error": [
        {
            "error_description": "Error description",
            "error_description_translated": "User facing error description"
        }
    ]
}
```

#### Object Type Indications <a href="#object-type-indications" id="object-type-indications"></a>

If the API returns different types of objects for the same field, they are nested in a group JSON object that, in its turn, contains a separate field for each of the objects. If you use one of the bunq SDKs, a BunqResponse object will be returned as the top level object.

**Example.** The *content* field can contain multiple types of objects such as ChatMessageContentText in this case. Be sure to follow this convention or use one of the bunq SDKs instead.

Copy

```
{
    "Response": [
        {
            "ChatMessage": {
                "id": 5,
                "content": {
                    "ChatMessageContentText": {
                        "text": "Hi! This is an automated security message. We saw you just logged in on an My Device Description. If you believe someone else logged in with your account, please get in touch with Support."
                    }
                }
            }
        }
    ]
}
```

#### &#x20;<a href="#time-formats" id="time-formats"></a>


# Signing

{% hint style="info" %}

## TL:DR On signing requests

#### Why we need signing

For some API calls we want to make sure the content tampered with. For instance making a payment, you would not want anyone to be able to change the amount or receiving party. Adding a signature allows bunq to detect whether the request body has been tampered with.

#### When do you need signing

Any request that creates or accepts a payment. If you forget to sign it we'll tell you with [error](/basics/errors) 466

#### What is a signature?

A signature is nothing more than a extra header: *X-Bunq-Client-Signature* that you add to your API call. You'll need to write some code in your application that takes in the request body and your private key (some examples are mentioned below)
{% endhint %}

**When Do You Need to Sign Requests?**

Most API calls don’t need signing, but **any request that creates or accepts a payment does**. If you forget to sign it, you’ll get a `466` error—so keep an eye out for that!

**How It Works**

* **You sign the request body** (not the whole request).
* **We verify the signature** on our end.
* **We sign our response**, so you can verify it too.

**What You Need**

* A **private key** (which you generate when installing your API connection).
* The **public key we send you**, which you’ll use to verify our responses.

## Example Curl request

This is a payment to Sugardaddy for €0,10. As you can see in the header we added the ***X-Bunq-Client-Signature*** header. This is just a Base64 encoded representation of the request body.

```
curl --location 'https://public-api.sandbox.bunq.com/v1/user/1800297/monetary-account/1989601/payment' \
--header [other headers]' \
--header 'X-Bunq-Client-Signature: eMymd9ynLx+j5tpcoPMlaJ7vEyxIZAXWInXIRxIOyP24KF7lpvNIwElB/wiQqVXr99pua4+onqvJUNiuceInAtohJnSGDEd58GjeBthz2OHTG5I0GZyJ9S8XAuBJYbqPgFaIk2m0VelPmjAHbiA3WSH0ASMDvcht/p4n60Y1GMVESXFovU2jUWX2X2k5QhowitIBJnsf4fkvYknbispFvUTn4ESU8QQVBgwR9QJ8DpxObGYbsTKQCyTzXoKNLM1mkw3pMwHn1EZ+AmOTx5iR/xXZLvvXWDmj9rEZWms7G1+drmhkK5aV8PuvR6s11mS6laRxyS1eoCaOcqeOZs1EcQ==' \
--data-raw '{
    "amount": {
        "value": "0.10",
        "currency": "EUR"
    },
    "counterparty_alias": {
        "type": "EMAIL",
        "value": "sugardaddy@bunq.com",
        "name": "Sugar Daddy"
    },
    "description": "My payment description :)"
}'
```

***

### Request signing example

Consider the following request, a `POST` to `/v1/user/126/monetary-account/222/payment` (the JSON is formatted with newlines and indentations to make it more readable - be careful to account for that when creating your signature):

| Header                        | Value                                                            |
| ----------------------------- | ---------------------------------------------------------------- |
| Cache-Control:                | no-cache                                                         |
| User-Agent:                   | bunq-TestServer/1.00 sandbox/0.17b3                              |
| X-Bunq-Client-Authentication: | f15f1bbe1feba25efb00802fa127042b54101c8ec0a524c36464f5bb143d3b8b |

```
{
    "amount": {
        "value": "12.50",
        "currency": "EUR"
    },
    "counterparty_alias": {
        "type": "EMAIL",
        "value": "bravo@bunq.com"
    },
    "description": "Payment for drinks."
}
```

Let's sign that request. First create a variable `$dataToSign` containing the body of the request:

```
{
    "amount": {
        "value": "12.50",
        "currency": "EUR"
    },
    "counterparty_alias": {
        "type": "EMAIL",
        "value": "bravo@bunq.com"
    },
    "description": "Payment for drinks."
}
```

Next, create the signature of `$dataToSign` using the SHA256 algorithm and the private key `$privateKey` of the Installation's key pair. In PHP, use the following to create a signature. The signature will be passed by reference into `$signature`.

`openssl_sign($dataToSign, $signature, $privateKey, OPENSSL_ALGO_SHA256);`

Encode the resulting `$signature` using base64, and add the resulting value to the request under the `X-Bunq-Client-Signature` header. You have just signed your request, and can send it!

## Troubleshooting

{% hint style="success" %}
If you struggle with signing you might want to try our [SDK's](broken://pages/ZOBhBQTwIMvx9EpWwDg6). They handle signing for you
{% endhint %}

If you are getting error 466 for invalid signatures then it's best to test these things:

Check list:

* [ ] Do you have a key pair generated?
* [ ] Are you using the correct encryption algorithms (Also check the padding algorithm)
* [ ] Sign ***only*** the request body (no headers, no URLs)
* [ ] Are you encoding your signature in base64 before sending
* [ ] There are no redundant characters (extra spaces, trailing line breaks, etc.) in the data to sign (Some programming languages or libraries might change the formatting to make it better human readable. This may cause the body to change and thus no longer match the signature)
* [ ] Make sure the body is appended to the data to sign exactly as you're adding it to the request.
* [ ] You have added the full body to the data to sign.
* [ ] You use the data to sign to create a SHA256 hash signature.
* [ ] You have base64 encoded the SHA256 hash signature before adding it to the request under `X-Bunq-Client-Signature`.

If you run into issues, double-check these points—or just use our SDKs, which handle all of this for you.

## Standards and formats

To ensure secure and valid request and response signatures, you should use the following standards: First, apply SHA256 as the cryptographic hash function to hash the data (either the request body or response body). For the signing process, use the private key with PKCS #1 v1.5 padding for RSA encryption to generate the signature. Once the signature is created, it must be base64-encoded to ensure proper transmission in HTTP headers. This ensures that the integrity and authenticity of the data are preserved, and the signature can be verified by the recipient using the corresponding public key.

## Response Signatures

In some cases you might also want to validate whether responses are actually sent by bunq. Here you can do the same thing. If you stored the public key of the bunq server (you received this during [installation](/tutorials/your-first-payment/creating-the-api-context)) you can verify the response sent by bunq and validate that the response body matches the signature.


# Python Code Example

## Introduction

Signing request is not hard, but it can be tricky if you don't understand the core concepts. That's why we provide example on how to implement this in Python. We'll do the following:

* Get a API key
* Get a Session, device and Installation on sandbox
* Get the monetary account and add some money
* Create a signature
* Use the signature to create a payment.

{% hint style="info" %}
Copy the files for [FastAPI](/basics/signing/python-code-example/full-main.py), [bunq\_lib](/basics/signing/python-code-example/full-bunq_lib.py) and [signing](/basics/signing/python-code-example/full-signing.py)
{% endhint %}

## What are we trying to achieve

Eventually we make an API call to create a payment. This is because this is one of the calls that requires us to generate a signature.

### What is a signature?

A signature is nothing more than a string of characters, but the exact string depends on what you use as input.

To give an example. Let's assume we want to sign the following payload:

```
{"hello":"World}
```

And now assume our signature for that payload is\
Signature: fafd6dad81f90a2d6f7d60a635f206188a54039ba07a84757cf9daf24a60b57a

bunq's backend can verify that that signature indeed belongs to that payload. Based on the public key that we shared earlier.

### How does this protect us?

If some attacker would send a different payload:

```
{"hello":"oops I changed the message"}
```

then that signature would change, and thus not be valid. A malicious attacker will also not be able to recreate a signature of his own, because they do not have our private\_key.pem.

## Getting started

we'll use the FastAPI framework. And will need FastApi, Cryptography and

We can install these by running: `pip install cryptography fastapi uvicorn`We'll have the following File structure

```
├── lib
│   └── bunq_lib.py
├── main.py
└── signing.py
```

### Let's create the main.py file first:

This is a quick scaffold that allows us to trigger actionts by visiting a endpoint in your browser.

{% hint style="info" %}
This file will not run on its own yet. We need the other files too.
{% endhint %}

Some things to note:

* You can see the bunq sandbox API key if you don't have one yet get yours here [API Keys](/basics/authentication/api-keys)
* You see we instantiate the Class BunqClient that we imported from \`the lib.bunq\_lib\`
* You'll only have to run \`uvicorn main:app --reload to start the server

```python
from fastapi import FastAPI
from lib.bunq_lib import BunqClient


USER_API_KEY = "sandbox_83f4f88a10706750ec2fdcbc1ce97b582a986f2846d33dcaaa974d95"

bunq_client = BunqClient(USER_API_KEY, service_name='signingScript')


# Run these 1x to initialize your application 
bunq_client.create_installation()
bunq_client.create_device_server()


bunq_client.create_session()

app = FastAPI()


@app.get("/monetary_account")
def get_monetary_account():
    response = bunq_client.request(endpoint='monetary-account',method='GET',data={})
    return response


@app.get("/request")
def request():
    endpoint = f"monetary-account/"
    response = bunq_client.request(endpoint=endpoint, method='GET', data=None)
    return response


@app.get("/payment")
def payment():
    payment = bunq_client.create_payment(
        amount='0.10', 
        recipient_iban='NL14RABO0169202917',
        currency='EUR',
        from_monetary_account_id='1989601', 
        description='test'
    )
    return payment

```

### Let's add signing.py

{% hint style="info" %}
get the full files [here](/basics/signing/python-code-example/full-signing.py)
{% endhint %}

We do this in a few steps:

1 is adding the imports and writing a function that generates the RSA Key pair for us (The function in the example is set up to check if one is generated first, if it is it loads the existing one)

```python

import os
from cryptography.hazmat.primitives import hashes, serialization
from cryptography.hazmat.primitives.asymmetric import padding, rsa
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives.serialization import load_pem_private_key
import base64
import hashlib

# Function to generate RSA key pair
def generate_rsa_key_pair():
    private_key_file = 'private_key.pem'
    public_key_file = 'public_key.pem'
    
    # Check if the key files exist
    if os.path.exists(private_key_file) and os.path.exists(public_key_file):
        # Read the existing keys from the text files
        with open(private_key_file, 'r') as private_file:
            private_key_pem = private_file.read()

        with open(public_key_file, 'r') as public_file:
            public_key_pem = public_file.read()

        print("bunq - using existing keypair")
    else:
        # Generate new RSA keys with 2048 bits as required by Bunq
        private_key = rsa.generate_private_key(
            public_exponent=65537,
            key_size=2048,
            backend=default_backend()
        )
        public_key = private_key.public_key()

        # Serialize private key to PEM format (PKCS#8 as required by Bunq)
        private_key_pem = private_key.private_bytes(
            encoding=serialization.Encoding.PEM,
            format=serialization.PrivateFormat.PKCS8,
            encryption_algorithm=serialization.NoEncryption()
        ).decode('utf-8')

        # Serialize public key to PEM format
        public_key_pem = public_key.public_bytes(
            encoding=serialization.Encoding.PEM,
            format=serialization.PublicFormat.SubjectPublicKeyInfo
        ).decode('utf-8')

        # Save the keys to text files
        with open(private_key_file, 'w') as private_file:
            private_file.write(private_key_pem)

        with open(public_key_file, 'w') as public_file:
            public_file.write(public_key_pem)

        print("bunq - creating new keypair [KEEP THESE FILES SAFE]")

    return private_key_pem, public_key_pem
    
    
```

The most important function in the signing.py file is our sign\_data() function that actually uses the generated keys and data we want to sign. Pay special attention to the specified hash and padding functions

```python
"""
This is a continuation of the signing.py file we were writting
"""
def sign_data(data, private_key_pem):
    """Signs the given data with the provided private key using SHA256 and PKCS#1 v1.5 padding.
    
    Args:
        data (str): The data to sign (should be the JSON request body)
        private_key_pem (str): The private key in PEM format
    
    Returns:
        str: Base64 encoded signature
    """
    private_key = load_private_key(private_key_pem)
    
    # Ensure the data is encoded in UTF-8 exactly as it will be sent
    encoded_data = data.encode('utf-8')

    # Debug: Print exact bytes being signed
    print("\n[DEBUG] Signing Data Bytes:", encoded_data)
    print("[DEBUG] SHA256 Hash of Data:", hashlib.sha256(encoded_data).hexdigest())

    # Generate signature using SHA256 and PKCS#1 v1.5 padding as required by Bunq
    signature = private_key.sign(
        encoded_data,
        padding.PKCS1v15(),
        hashes.SHA256()
    )

    # Encode in Base64 (as required by Bunq API)
    encoded_signature = base64.b64encode(signature).decode('utf-8')

    # Debug: Print signature
    print("[DEBUG] Base64 Encoded Signature:", encoded_signature)

    return encoded_signature
```

We can call this function from our bunq\_lib.py to generate a signature for each request we want to sign.

## Ok now let's make this in bunq\_lib.py

We start with the imports and creating a class, with variables for API keys, tokens and everything else we need. You can also see the 2 functions one to generate a device token, and one to load it if it already exists.

```python
import json
import requests
from signing import generate_rsa_key_pair, sign_data, verify_response
import uuid


class BunqClient:
    def __init__(self, api_key, service_name, base_url="https://public-api.sandbox.bunq.com/v1"):
        self.service_name = service_name
        self.api_key = api_key
        self.private_key_pem, self.public_key_pem = generate_rsa_key_pair()
        self.device_token = None
        self.server_public_key = None
        self.device_server_id = None
        self.session_token = None
        self.user_id = None
        self.base_url = base_url

        # Try to load device token from file
        self.load_device_token()
        
    def save_device_token(self):
        """Save the device token to a file."""
        with open('device_token.json', 'w') as file:
            json.dump({"device_token": self.device_token}, file)

    def load_device_token(self):
        """Load the device token from a file if it exists."""
        try:
            with open('device_token.json', 'r') as file:
                data = json.load(file)
                self.device_token = data.get("device_token")
                print(f"bunq - Loaded device token from file [KEEP THIS SAFE!]")
        except FileNotFoundError:
            print("bunq - No device token found, need to create a new one.")

```

## Creating installation, registering the device and getting a session

In the same file as above we add a few more functions that allow us to create a installation, device and session<br>

```python
    """
    This is a continuation of the bunq_lib.py file we were writting
    """
    
    def create_installation(self):
        if self.device_token is not None:
            print("bunq - Device token already created.")
            return

        url = f"{self.base_url}/installation"
        payload = json.dumps({"client_public_key": self.public_key_pem})

        headers = {
            'Content-Type': 'application/json',
            'User-Agent': self.service_name,
            'X-Bunq-Language': 'en_US',
            'X-Bunq-Region': 'nl_NL',
            'X-Bunq-Geolocation': '0 0 0 0 000',
        }

        response = requests.post(url, headers=headers, data=payload)
        data = response.json()

        self.device_token = next(item["Token"]["token"] for item in data["Response"] if "Token" in item)
        self.server_public_key = next(item["ServerPublicKey"]["server_public_key"] for item in data["Response"] if "ServerPublicKey" in item)
        self.save_device_token()  # Save the token for future use

    def create_device_server(self):
        if not self.device_token:
            print("bunq - Device token is required to create device server.")
            return

        url = f"{self.base_url}/device-server"
        payload = json.dumps({
            "description": self.service_name,
            "secret": self.api_key,
            "permitted_ips": ["*"]
        })
        signed_payload_signature = sign_data(payload, self.private_key_pem)

        headers = {
            'Content-Type': 'application/json',
            'Cache-Control': 'no-cache',
            'User-Agent': self.service_name,
            'X-Bunq-Language': 'en_US',
            'X-Bunq-Region': 'nl_NL',
            'X-Bunq-Geolocation': '0 0 0 0 000',
            'X-Bunq-Client-Authentication': self.device_token,
            'X-Bunq-Client-Signature': signed_payload_signature
        }

        response = requests.post(url, headers=headers, data=payload)
        self.device_server_id = response.text

    def create_session(self):
        if not self.device_token:
            print("bunq - Device token is required to create session.")
            return

        url = f"{self.base_url}/session-server"
        payload_dict = {"secret": self.api_key}
        payload_json = json.dumps(payload_dict, separators=(',', ':'))
        signed_payload_signature = sign_data(payload_json, self.private_key_pem)
        
        headers = {
            'Content-Type': 'application/json',
            'Cache-Control': 'no-cache',
            'User-Agent': self.service_name,
            'X-Bunq-Language': 'en_US',
            'X-Bunq-Region': 'nl_NL',
            'X-Bunq-Geolocation': '0 0 0 0 000',
            'X-Bunq-Client-Authentication': self.device_token,
            'X-Bunq-Client-Signature': signed_payload_signature
        }

        response = requests.post(url, headers=headers, data=payload_json)
        data = response.json()
        print(data)
        # Extract and save session token
        self.session_token = next(item["Token"]["token"] for item in data["Response"] if "Token" in item)
        self.user_id = next(item["UserPerson"]["id"] for item in data["Response"] if "UserPerson" in item)

        print(f"bunq - Session Token: {self.session_token}")
        print(f"bunq - User ID: {self.user_id}")
```

At this point your you could run the fastapi server with the command

```
uvicorn main:app --reload
```

That will start the server and will register your device and create the private and public keys. Your file tree should now look like this

```
├── device_token.json
├── lib
│   └── bunq_lib.py
├── main.py
├── private_key.pem
├── public_key.pem
└── signing.py
```

{% hint style="warning" %}
Be aware that both the device\_token.json as the private\_key.pem contain credentials that should not be commited to source control or be shared in general. We recommend adding them to your environment variables. We created them as files here to be transparent on what the content looks like
{% endhint %}

Given that we now have a session and can make calls we can finish the rest of our file.

## Let's make a payment and a request

In the rest of the file you can see 2 methods\
1\. request. This is just a generic function that can create a API call to a endpoint of your choosing.

2. The second function is to create a payment. This one is a bit more tricky as it requires us to sign.

```python
"""
This is a continuation of the bunq_lib.py file we were writting
"""
    def request(self, endpoint: str, method: str = "GET", data: dict = None):
        url = f"{self.base_url}/user/{self.user_id}/{endpoint}"
        print(f"[DEBUG] bunq - Requesting: {method} {url}")

        # Default headers
        headers = {
            'Content-Type': 'application/json',
            'Cache-Control': 'no-cache',
            'User-Agent': self.service_name,
            'X-Bunq-Language': 'en_US',
            'X-Bunq-Region': 'nl_NL',
            'X-Bunq-Geolocation': '0 0 0 0 000',
            'X-Bunq-Client-Authentication': self.session_token,
            'X-Bunq-Client-Request-Id': str(uuid.uuid4())  # Should be unique for each request
        }

        payload = None
            
        if data and method == "POST":
            # Ensure consistent JSON formatting by using separators
            payload = json.dumps(data, separators=(',', ':'))
            signed_payload_signature = sign_data(payload, self.private_key_pem)
            headers["X-Bunq-Client-Signature"] = signed_payload_signature

            print(f"[DEBUG] Request Payload: {payload}")
            print(f"[DEBUG] Signed Payload Signature: {signed_payload_signature}")

        try:
            response = requests.request(method, url, headers=headers, data=payload)
            print(f"[DEBUG] Response Status Code: {response.status_code}")

            if response.status_code == 401:
                print("[WARNING] Unauthorized (401) - Refreshing session...")
                self.refresh_session()
                response = requests.request(method, url, headers=headers, data=payload)
                print(f"[DEBUG] Retried Response Status Code: {response.status_code}")

            if response.status_code == 200:
                response_body = response.text
                server_signature = response.headers.get('X-Bunq-Server-Signature')
                
                if server_signature and self.server_public_key:
                    # Verify the response signature
                    if not verify_response(response_body, server_signature, self.server_public_key):
                        raise Exception("Response signature verification failed")
                    print("[DEBUG] Response signature verified successfully")
                
                return response.json()

            print(f"[ERROR] Request failed: {response.status_code} - {response.text}")
            response.raise_for_status()

        except requests.exceptions.RequestException as e:
            print(f"[ERROR] Request error: {e}")
            raise
    


    def create_payment(self, amount: str, recipient_iban: str, currency: str, from_monetary_account_id: str, description: str):
        url = f"{self.base_url}/user/{self.user_id}/monetary-account/{from_monetary_account_id}/payment"

        payload = json.dumps({
            "amount": {
                "value": str(amount),
                "currency": str(currency)
            },
            "counterparty_alias": {
                "type": "EMAIL",
                "value": "sugardaddy@bunq.com",
                "name": "Sugar Daddy"
            },
            "description": str(description)
        }, separators=(',', ':'))  # Ensure consistent JSON formatting
        
        signature = sign_data(payload, self.private_key_pem)
        headers = {
            'Content-Type': 'application/json',
            'Cache-Control': 'no-cache',
            'User-Agent': self.service_name,
            'X-Bunq-Language': 'en_US',
            'X-Bunq-Region': 'nl_NL',
            'X-Bunq-Client-Request-Id': str(uuid.uuid4()),
            'X-Bunq-Geolocation': '0 0 0 0 000',
            'X-Bunq-Client-Authentication': self.session_token,
            'X-Bunq-Client-Signature': signature
        }

        response = requests.post(url, headers=headers, data=payload)
        
        if response.status_code == 200:
            response_body = response.text
            server_signature = response.headers.get('X-Bunq-Server-Signature')
            
            if server_signature and self.server_public_key:
                # Verify the response signature
                if not verify_response(response_body, server_signature, self.server_public_key):
                    raise Exception("Response signature verification failed")
                print("[DEBUG] Response signature verified successfully")
        
        return response.json()

                
```

## Making a generic API call

In our main.py file we started a fastAPI server which has 2 endpoints\
1\. /get\_cards - Which returns the cards\
2\. /monetary-account which returns the monetary accounts of the user.

You can see they both use the .request() method that we defined in the bunq\_lib.py. They just call a different endpoint.

```python
@app.get("/get_cards")
def get_cards():
    response = bunq_client.request(endpoint='card',method='GET',data={})
    return response


@app.get("/monetary_account")
def get_monetary_account():
    response = bunq_client.request(endpoint='monetary-account',method='GET',data={})
    return response


```

If we go to our browser and type:\
<http://localhost:8000/monetary_account>

We'll get the response object with all our monetary accounts:

```
{
  "Response": [
    {
      "MonetaryAccountBank": {
        "id": 1989601,
        "created": "2025-02-26 16:14:21.031964",
        "updated": "2025-02-26 16:14:21.031964",
        "alias": [
          {
            "type": "PHONE_NUMBER",
            "value": "+31616459904",
            "name": "+31616459904"
          },
          {
            "type": "EMAIL",
            "value": "test+845d47ed-dced-4a0c-8b11-57b5e9ff7288@bunq.com",
            "name": "test+845d47ed-dced-4a0c-8b11-57b5e9ff7288@bunq.com"
          },
          {
            "type": "IBAN",
            "value": "NL80BUNQ2118573685",
            "name": "Donald Byrne"
          }
        ],
        "avatar": {
          "uuid": "b07adec4-2659-460b-869e-fa2fb96257e8",
          "image": [
            {
              "attachment_public_uuid": "03aab32d-26f6-48e2-8133-4e0c7ffb1dab",
              "height": 1023,
              "width": 1024,
              "content_type": "image/png",
              "urls": [
                {
                  "type": "ORIGINAL",
                  "url": "https://bunq-triage-model-storage-public.s3.eu-central-1.amazonaws.com/bunq_file/File/content/921ece497cd00f4e0cef3f0f63a962c31cf3f8e35311d127d5a7b23be3d074d5.png"
                }
              ]
            }
          ],
          "anchor_uuid": "392ee3d9-45b0-435f-bd0e-cd7e2e4b8b26",
          "style": "NONE"
        },
        "balance": {
          "currency": "EUR",
          "value": "998.30"
        },
        "country": "NL",
        "currency": "EUR",
        "display_name": "D. Byrne",
        "daily_limit": {
          "currency": "EUR",
          "value": "5000.00"
        },
        "description": "Main",
        "public_uuid": "392ee3d9-45b0-435f-bd0e-cd7e2e4b8b26",
        "status": "ACTIVE",
        "sub_status": "NONE",
        "timezone": "europe/amsterdam",
        "user_id": 1800297,
        "monetary_account_profile": {
          "profile_fill": {
            "status": "ACTIVE",
            "balance_preferred": {
              "currency": "EUR",
              "value": "100.00"
            },
            "balance_threshold_low": {
              "currency": "EUR",
              "value": "50.00"
            }
          },
          "profile_drain": null,
          "profile_action_required": "NO_ACTION_NEEDED",
          "profile_amount_required": {
            "currency": "EUR",
            "value": "0.00"
          }
        },
        "setting": {
          "color": "#FF7819",
          "icon": null,
          "default_avatar_status": "AVATAR_DEFAULT",
          "restriction_chat": "ALLOW_INCOMING",
          "sdd_expiration_action": "AUTO_ACCEPT"
        },
        "connected_cards": [],
        "budget": [],
        "overdraft_limit": {
          "currency": "EUR",
          "value": "0.00"
        },
        "all_auto_save_id": []
      }
    }
  ],
  "Pagination": {
    "future_url": "/v1/user/1800297/monetary-account?newer_id=1989601",
    "newer_url": null,
    "older_url": null
  }
}
```

## Now let's also make a payment:

The set up is the same we simply navigate to:\
<http://localhost:8000/payment> to trigger the call to the endpoint

and get a response with the payment ID:<br>

```
{
  "Response": [
    {
      "Id": {
        "id": 25083708
      }
    }
  ]
}
```

Let's go through it step-by step. To explain the signing in depth.

When we go to the /payment route we trigger this code:<br>

```python
@app.get("/payment")
def payment():
    payment = bunq_client.create_payment(
        amount='0.10', 
        recipient_iban='NL14RABO0169202917',
        currency='EUR',
        from_monetary_account_id='18375', 
        description='test'
    )
    return payment

```

This calls the create\_payment() function from the bunq\_client class in our bunq\_lib.py file. It sends along some payment details, like the receiver IBAN, the monetary account to pay from, a description and an amount.

Now what happens in the create\_payment() function<br>

```python
def create_payment(self, amount: str, recipient_iban: str, currency: str, from_monetary_account_id: str, description: str):
        url = f"{self.base_url}/user/{self.user_id}/monetary-account/{from_monetary_account_id}/payment"

        payload = json.dumps({
            "amount": {
                "value": str(amount),
                "currency": str(currency)
            },
            "counterparty_alias": {
                "type": "EMAIL",
                "value": "sugardaddy@bunq.com",
                "name": "Sugar Daddy"
            },
            "description": str(description)
        }, separators=(',', ':'))  # Ensure consistent JSON formatting
        
        signature = sign_data(payload, self.private_key_pem)
        headers = {
            'Content-Type': 'application/json',
            'Cache-Control': 'no-cache',
            'User-Agent': self.service_name,
            'X-Bunq-Language': 'en_US',
            'X-Bunq-Region': 'nl_NL',
            'X-Bunq-Client-Request-Id': str(uuid.uuid4()),
            'X-Bunq-Geolocation': '0 0 0 0 000',
            'X-Bunq-Client-Authentication': self.session_token,
            'X-Bunq-Client-Signature': signature
        }

        response = requests.post(url, headers=headers, data=payload)
        
        if response.status_code == 200:
            response_body = response.text
            server_signature = response.headers.get('X-Bunq-Server-Signature')
            
            if server_signature and self.server_public_key:
                # Verify the response signature
                if not verify_response(response_body, server_signature, self.server_public_key):
                    raise Exception("Response signature verification failed")
                print("[DEBUG] Response signature verified successfully")
        
        return response.json()

                
```

1. We first create a payload. We have to be very specific in the exact formatting of the payload. That's why we specify the separators. We do this to ensure the payload that we sign and the payload that we send with the request match exactly. Else the signature will fail.
2. Now we call the sign\_data() function from our signing.py file this will return a signature based on our private key and the payload.
3. We add that signature to our 'X-Bunq-Client-Signature'.
4. From there it is exactly like any API call where you wait for a 200 response status
5. We included 1 final step which is to also validate the 'X-Bunq-Server-Signature' signature. by calling the verify\_response() function and comparing the signature bunq returned to the public key we stored in the installation step.


# Full main.py

```python
from fastapi import FastAPI
from lib.bunq_lib import BunqClient


USER_API_KEY = "sandbox_83f4f88a10706750ec2fdcbc1ce97b582a986f2846d33dcaaa974d95"

bunq_client = BunqClient(USER_API_KEY, service_name='PeterScript')


# Run these 1x to initialize your application 
bunq_client.create_installation()
bunq_client.create_device_server()


bunq_client.create_session()

app = FastAPI()


@app.get("/monetary_account")
def get_monetary_account():
    response = bunq_client.request(endpoint='monetary-account',method='GET',data={})
    return response



@app.get("/request")
def request():
    endpoint = f"monetary-account/"
    response = bunq_client.request(endpoint=endpoint, method='GET', data=None)
    return response

@app.get("/payment")
def payment():
    payment = bunq_client.create_payment(
        amount='0.10', 
        recipient_iban='NL14RABO0169202917',
        currency='EUR',
        from_monetary_account_id='1989601', 
        description='test'
    )
    return payment

```


# Full bunq\_lib.py

```python
import json
import requests
from signing import generate_rsa_key_pair, sign_data, verify_response
import uuid


class BunqClient:
    def __init__(self, api_key, service_name, base_url="https://public-api.sandbox.bunq.com/v1"):
        self.service_name = service_name
        self.api_key = api_key
        self.private_key_pem, self.public_key_pem = generate_rsa_key_pair()
        self.device_token = None
        self.server_public_key = None
        self.device_server_id = None
        self.session_token = None
        self.user_id = None
        self.base_url = base_url

        # Try to load device token from file
        self.load_device_token()

    def save_device_token(self):
        """Save the device token to a file."""
        with open('device_token.json', 'w') as file:
            json.dump({"device_token": self.device_token}, file)

    def load_device_token(self):
        """Load the device token from a file if it exists."""
        try:
            with open('device_token.json', 'r') as file:
                data = json.load(file)
                self.device_token = data.get("device_token")
                print(f"bunq - Loaded device token from file [KEEP THIS SAFE!]")
        except FileNotFoundError:
            print("bunq - No device token found, need to create a new one.")

    def create_installation(self):
        if self.device_token is not None:
            print("bunq - Device token already created.")
            return

        url = f"{self.base_url}/installation"
        payload = json.dumps({"client_public_key": self.public_key_pem})

        headers = {
            'Content-Type': 'application/json',
            'User-Agent': self.service_name,
            'X-Bunq-Language': 'en_US',
            'X-Bunq-Region': 'nl_NL',
            'X-Bunq-Geolocation': '0 0 0 0 000',
        }

        response = requests.post(url, headers=headers, data=payload)
        data = response.json()

        self.device_token = next(item["Token"]["token"] for item in data["Response"] if "Token" in item)
        self.server_public_key = next(item["ServerPublicKey"]["server_public_key"] for item in data["Response"] if "ServerPublicKey" in item)
        self.save_device_token()  # Save the token for future use

    def create_device_server(self):
        if not self.device_token:
            print("bunq - Device token is required to create device server.")
            return

        url = f"{self.base_url}/device-server"
        payload = json.dumps({
            "description": self.service_name,
            "secret": self.api_key,
            "permitted_ips": ["*"]
        })
        signed_payload_signature = sign_data(payload, self.private_key_pem)

        headers = {
            'Content-Type': 'application/json',
            'Cache-Control': 'no-cache',
            'User-Agent': self.service_name,
            'X-Bunq-Language': 'en_US',
            'X-Bunq-Region': 'nl_NL',
            'X-Bunq-Geolocation': '0 0 0 0 000',
            'X-Bunq-Client-Authentication': self.device_token,
            'X-Bunq-Client-Signature': signed_payload_signature
        }

        response = requests.post(url, headers=headers, data=payload)
        self.device_server_id = response.text

    def create_session(self):
        if not self.device_token:
            print("bunq - Device token is required to create session.")
            return

        url = f"{self.base_url}/session-server"
        payload_dict = {"secret": self.api_key}
        payload_json = json.dumps(payload_dict, separators=(',', ':'))
        signed_payload_signature = sign_data(payload_json, self.private_key_pem)
        
        headers = {
            'Content-Type': 'application/json',
            'Cache-Control': 'no-cache',
            'User-Agent': self.service_name,
            'X-Bunq-Language': 'en_US',
            'X-Bunq-Region': 'nl_NL',
            'X-Bunq-Geolocation': '0 0 0 0 000',
            'X-Bunq-Client-Authentication': self.device_token,
            'X-Bunq-Client-Signature': signed_payload_signature
        }

        response = requests.post(url, headers=headers, data=payload_json)
        data = response.json()
        print(data)
        # Extract and save session token
        self.session_token = next(item["Token"]["token"] for item in data["Response"] if "Token" in item)
        self.user_id = next(item["UserPerson"]["id"] for item in data["Response"] if "UserPerson" in item)

        print(f"bunq - Session Token: {self.session_token}")
        print(f"bunq - User ID: {self.user_id}")
        

    def request(self, endpoint: str, method: str = "GET", data: dict = None):
        url = f"{self.base_url}/user/{self.user_id}/{endpoint}"
        print(f"[DEBUG] bunq - Requesting: {method} {url}")

        # Default headers
        headers = {
            'Content-Type': 'application/json',
            'Cache-Control': 'no-cache',
            'User-Agent': self.service_name,
            'X-Bunq-Language': 'en_US',
            'X-Bunq-Region': 'nl_NL',
            'X-Bunq-Geolocation': '0 0 0 0 000',
            'X-Bunq-Client-Authentication': self.session_token,
            'X-Bunq-Client-Request-Id': str(uuid.uuid4())  # Should be unique for each request
        }

        payload = None
            
        if data and method == "POST":
            # Ensure consistent JSON formatting by using separators
            payload = json.dumps(data, separators=(',', ':'))
            signed_payload_signature = sign_data(payload, self.private_key_pem)
            headers["X-Bunq-Client-Signature"] = signed_payload_signature

            print(f"[DEBUG] Request Payload: {payload}")
            print(f"[DEBUG] Signed Payload Signature: {signed_payload_signature}")

        try:
            response = requests.request(method, url, headers=headers, data=payload)
            print(f"[DEBUG] Response Status Code: {response.status_code}")

            if response.status_code == 401:
                print("[WARNING] Unauthorized (401) - Refreshing session...")
                self.refresh_session()
                response = requests.request(method, url, headers=headers, data=payload)
                print(f"[DEBUG] Retried Response Status Code: {response.status_code}")

            if response.status_code == 200:
                response_body = response.text
                server_signature = response.headers.get('X-Bunq-Server-Signature')
                
                if server_signature and self.server_public_key:
                    # Verify the response signature
                    if not verify_response(response_body, server_signature, self.server_public_key):
                        raise Exception("Response signature verification failed")
                    print("[DEBUG] Response signature verified successfully")
                
                return response.json()

            print(f"[ERROR] Request failed: {response.status_code} - {response.text}")
            response.raise_for_status()

        except requests.exceptions.RequestException as e:
            print(f"[ERROR] Request error: {e}")
            raise
    


    def create_payment(self, amount: str, recipient_iban: str, currency: str, from_monetary_account_id: str, description: str):
        url = f"{self.base_url}/user/{self.user_id}/monetary-account/{from_monetary_account_id}/payment"

        payload = json.dumps({
            "amount": {
                "value": str(amount),
                "currency": str(currency)
            },
            "counterparty_alias": {
                "type": "EMAIL",
                "value": "sugardaddy@bunq.com",
                "name": "Sugar Daddy"
            },
            "description": str(description)
        }, separators=(',', ':'))  # Ensure consistent JSON formatting
        
        signature = sign_data(payload, self.private_key_pem)
        headers = {
            'Content-Type': 'application/json',
            'Cache-Control': 'no-cache',
            'User-Agent': self.service_name,
            'X-Bunq-Language': 'en_US',
            'X-Bunq-Region': 'nl_NL',
            'X-Bunq-Client-Request-Id': str(uuid.uuid4()),
            'X-Bunq-Geolocation': '0 0 0 0 000',
            'X-Bunq-Client-Authentication': self.session_token,
            'X-Bunq-Client-Signature': signature
        }

        response = requests.post(url, headers=headers, data=payload)
        
        if response.status_code == 200:
            response_body = response.text
            server_signature = response.headers.get('X-Bunq-Server-Signature')
            
            if server_signature and self.server_public_key:
                # Verify the response signature
                if not verify_response(response_body, server_signature, self.server_public_key):
                    raise Exception("Response signature verification failed")
                print("[DEBUG] Response signature verified successfully")
        
        return response.json()

                
```


# Full signing.py

```python
import os
from cryptography.hazmat.primitives import hashes, serialization
from cryptography.hazmat.primitives.asymmetric import padding, rsa
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives.serialization import load_pem_private_key
import base64
import hashlib


# Function to generate RSA key pair
def generate_rsa_key_pair():
    private_key_file = 'private_key.pem'
    public_key_file = 'public_key.pem'
    
    # Check if the key files exist
    if os.path.exists(private_key_file) and os.path.exists(public_key_file):
        # Read the existing keys from the text files
        with open(private_key_file, 'r') as private_file:
            private_key_pem = private_file.read()

        with open(public_key_file, 'r') as public_file:
            public_key_pem = public_file.read()

        print("bunq - using existing keypair")
    else:
        # Generate new RSA keys with 2048 bits as required by Bunq
        private_key = rsa.generate_private_key(
            public_exponent=65537,
            key_size=2048,
            backend=default_backend()
        )
        public_key = private_key.public_key()

        # Serialize private key to PEM format (PKCS#8 as required by Bunq)
        private_key_pem = private_key.private_bytes(
            encoding=serialization.Encoding.PEM,
            format=serialization.PrivateFormat.PKCS8,
            encryption_algorithm=serialization.NoEncryption()
        ).decode('utf-8')

        # Serialize public key to PEM format
        public_key_pem = public_key.public_bytes(
            encoding=serialization.Encoding.PEM,
            format=serialization.PublicFormat.SubjectPublicKeyInfo
        ).decode('utf-8')

        # Save the keys to text files
        with open(private_key_file, 'w') as private_file:
            private_file.write(private_key_pem)

        with open(public_key_file, 'w') as public_file:
            public_file.write(public_key_pem)

        print("bunq - creating new keypair [KEEP THESE FILES SAFE]")

    return private_key_pem, public_key_pem


def load_private_key(private_key_pem):
    """Load a private key from PEM format."""
    return load_pem_private_key(private_key_pem.encode(), password=None, backend=default_backend())


def load_public_key(public_key_pem):
    """Load a public key from PEM format."""
    return serialization.load_pem_public_key(
        public_key_pem.encode(),
        backend=default_backend()
    )


def sign_data(data, private_key_pem):
    """Signs the given data with the provided private key using SHA256 and PKCS#1 v1.5 padding.
    
    Args:
        data (str): The data to sign (should be the JSON request body)
        private_key_pem (str): The private key in PEM format
    
    Returns:
        str: Base64 encoded signature
    """
    private_key = load_private_key(private_key_pem)
    
    # Ensure the data is encoded in UTF-8 exactly as it will be sent
    encoded_data = data.encode('utf-8')

    # Debug: Print exact bytes being signed
    print("\n[DEBUG] Signing Data Bytes:", encoded_data)
    print("[DEBUG] SHA256 Hash of Data:", hashlib.sha256(encoded_data).hexdigest())

    # Generate signature using SHA256 and PKCS#1 v1.5 padding as required by Bunq
    signature = private_key.sign(
        encoded_data,
        padding.PKCS1v15(),
        hashes.SHA256()
    )

    # Encode in Base64 (as required by Bunq API)
    encoded_signature = base64.b64encode(signature).decode('utf-8')

    # Debug: Print signature
    print("[DEBUG] Base64 Encoded Signature:", encoded_signature)

    return encoded_signature


def verify_response(response_body, signature, server_public_key_pem):
    """Verifies the server's response signature.
    
    Args:
        response_body (str): The response body as a string
        signature (str): The base64 encoded signature from X-Bunq-Server-Signature header
        server_public_key_pem (str): The server's public key in PEM format
    
    Returns:
        bool: True if signature is valid, False otherwise
    """
    try:
        # Load the server's public key
        public_key = load_public_key(server_public_key_pem)
        
        # Decode the base64 signature
        decoded_signature = base64.b64decode(signature)
        
        # Verify the signature
        public_key.verify(
            decoded_signature,
            response_body.encode('utf-8'),
            padding.PKCS1v15(),
            hashes.SHA256()
        )
        return True
    except Exception as e:
        print(f"[ERROR] Signature verification failed: {e}")
        return False


```


# PHP Code Example

#### Signing a Request (Example in PHP)

1️⃣ Take the request body:

```json
{
    "amount": {
        "value": "12.50",
        "currency": "EUR"
    },
    "counterparty_alias": {
        "type": "EMAIL",
        "value": "bravo@bunq.com"
    },
    "description": "Payment for drinks."
}
```

2️⃣ Sign it using your private key:

```php
openssl_sign($dataToSign, $signature, $privateKey, OPENSSL_ALGO_SHA256);
$encodedSignature = base64_encode($signature);
```

3️⃣ Add the signature to the request headers:

```http
X-Bunq-Client-Signature: [your base64-encoded signature]
```

4️⃣ Send it! 🎉


# Whitelisting and Updating IP addresses

### Why Whitelist IP Addresses?

Whitelisting IP addresses is a critical security measure that restricts which API calls are accepted by our backend. By explicitly listing trusted IP addresses, you minimize the risk of unauthorized access from unknown or malicious locations. This ensures that even if someone obtains your password credential, they cannot authenticate unless their IP is authorized.

In short: **IP whitelisting helps protect your account by allowing server authentication only from trusted networks.**

### Default Behavior

The IP address you pass along in you [Device Registration](/tutorials/your-first-payment/creating-the-api-context/device-registration) is automatically whitelisted to be used for API calls and set to ACTIVE. If you pass in a wildcard `["*"]` then we chose the IP address used for the API call. Whitelisting IP addresses is risky and only possible from within our app.

So if you don't change anything this will be the default behavior

* The IP address you use during device registeration will be immidiatly usable.
* Other IPs are not allowed until explicitly added.
* This default auto-whitelisting ensures smooth onboarding without manual IP setup.

***

### Key Concepts

#### CredentialPasswordIp

This is your credential linked to your [Devices](/devices) used for server-side authentication. Each credential has IP addresses associated with it.

#### IP Entries

Each credential can have multiple IP addresses whitelisted, with the following attributes:

* **ip**: The IP address allowed to authenticate.
* **status**: Either
  * `ACTIVE`: This IP is allowed to authenticate (and will be billed).
  * `INACTIVE`: This IP is blocked and cannot be used.

> **Note:** IP addresses cannot be changed once created. To update an IP, you must mark the old one as `INACTIVE` and add a new one.

***

### Important Notes

* The wildcard IP `*` (which disables IP filtering, allowing all IPs) **cannot be created or managed via the API** — only via the bunq app. This is to prevent accidental security risks through API misuse.
* Only `ACTIVE` IP addresses can be used to authenticate.
* Carefully manage your IP whitelist to control access and reduce attack surface.

***

### API Operations Overview

#### Reference

Visit this page for all the API calls related to IP whitelisting: [Credential Password IP](/credential-password-ip)

#### Credentials Management

* **List Credentials**\
  `GET /v1/user/{user_id}/credential-password-ip`\
  Lists all password credentials for your user.
* **Get Credential**\
  `GET /v1/user/{user_id}/credential-password-ip/{credential_password_ip_id}`\
  Retrieves details for a specific credential.

#### IP Addresses Management

* **List IPs for a Credential**\
  `GET /v1/user/{user_id}/credential-password-ip/{credential_password_ip_id}/ip`\
  Lists all IPs whitelisted for the credential.
* **Get a Single IP Entry**\
  `GET /v1/user/{user_id}/credential-password-ip/{credential_password_ip_id}/ip/{item_id}`\
  Retrieves details of a specific IP entry.
* **Add a New IP**\
  `POST /v1/user/{user_id}/credential-password-ip/{credential_password_ip_id}/ip`\
  Adds a new IP address with a status (`ACTIVE` or `INACTIVE`).
* **Update IP Status**\
  `PUT /v1/user/{user_id}/credential-password-ip/{credential_password_ip_id}/ip/{item_id}`\
  Updates the status from `ACTIVE` to `INACTIVE` of an existing IP.\
  \&#xNAN;*Note:* The IP address itself **cannot** be changed.

***

### Security Best Practices

* Regularly review and update your IP whitelist to remove unused or suspicious IPs.
* Avoid using the wildcard IP `*` unless absolutely necessary and only via the bunq app.
* Set inactive IPs to `INACTIVE` rather than deleting immediately for audit trails.
* Use distinct credentials per environment (dev, staging, prod) with tailored IP whitelists.

***


# Chapter 0 setting up the API context

When integrating with the bunq API, security is a top priority. Instead of relying solely on API keys, bunq uses a **three-step authentication process:**

* installation,
* device registration,
* session creation

to ensure secure and controlled access. This approach prevents unauthorized use, protects sensitive financial data, and aligns with banking security standards like PSD2. Here’s how each step enhances security while keeping API access flexible and manageable.

{% hint style="info" %}
**Pro Tip: Simplify with Postman** Using our Postman examples makes API integration super easy. We've pre-filled all the requests with environment variables, streamlining the process and reducing the risk of errors. It's by far the quickest way to get a live session, to play around with until you fully commit to a integration in your own application.This setup allows you to focus more on development and less on configuration, ensuring a smoother experience

Check it out here : [Postman](/getting-started/tools/postman)
{% endhint %}

**Setting up the API context**

We documented the API context in the following pages

* [Creating the Installation](/tutorials/your-first-payment/creating-the-api-context/creating-the-installation)
* [Device Registration](/tutorials/your-first-payment/creating-the-api-context/device-registration)
* [Start a Session](/tutorials/your-first-payment/creating-the-api-context/start-a-session)

### Host URL for the API endpoints <a href="#host-url-for-the-api-endpoints" id="host-url-for-the-api-endpoints"></a>

One change for partners, compared to regular API users is the base URL. Make sure to target

* Sandbox: `https://partner-api.sandbox.bunq.com`
* Production: `https://api.partner.bunq.com`


# Chapter 1 Setting up oauth client

**This chapter goes into the setting up of the oauth client for user provisioning.**

{% hint style="info" %}
Our default API documentation has additional material related to setting up[OAuth](/basics/authentication/oauth).
{% endhint %}

**Endpoint:**

```http
POST /v1/user/{user_id}/oauth-client

{}
```

**Response:**

```json
{
    "Response": [
        {
            "Id": {
                "id": 789
            }
        }
    ]
}
```

***

## Step 2: Read OAuth Client Details

**Endpoint:**

```http
GET /v1/user/{user_id}/oauth-client/{client_id}
```

**Response:**

```json
{
    "Response": [
        {
            "OauthClient": {
                "id": 789,
                "status": "ACTIVE",
                "display_name": null,
                "client_id": "client_abc123def456",
                "secret": "secret_xyz789uvw012",
                "callback_url": []
            }
        }
    ]
}
```

**Fields:**

| Field          | Type    | Description                            |
| -------------- | ------- | -------------------------------------- |
| `id`           | integer | Unique identifier for the OAuth client |
| `status`       | string  | Status (e.g., `ACTIVE`)                |
| `display_name` | string  | Human-readable name                    |
| `client_id`    | string  | OAuth client ID                        |
| `secret`       | string  | OAuth client secret                    |
| `callback_url` | array   | Registered callback URLs               |

***

## Step 3: Register OAuth Callback URL

**Endpoint:**

```http
POST /v1/user/{user_id}/oauth-client/{client_id}/callback-url

{
    "url": "https://yourpartner.com/oauth/callback"
}
```

**Response:**

```json
{
    "Response": [
        {
            "Id": {
                "id": 456
            }
        }
    ]
}
```

| Parameter | Type   | Required | Description                    |
| --------- | ------ | -------- | ------------------------------ |
| `url`     | string | Yes      | HTTPS callback URL to register |

***

## Step 4: Read Callback URL Details

**Endpoint:**

```http
GET /v1/user/{user_id}/oauth-client/{client_id}/callback-url/{callback_url_id}
```

**Response:**

```json
{
    "Response": [
        {
            "OauthCallbackUrl": {
                "id": 456,
                "created": "2025-08-13 14:45:20.123456",
                "updated": "2025-08-13 14:45:20.123456",
                "url": "https://yourpartner.com/oauth/callback"
            }
        }
    ]
}
```


# Chapter 2 Creating a user provision

Provisioning a user is the initial step in the process of creating a user account within the bunq platform. This step involves assigning basic user attributes and configurations, such as unique identifiers, contact method, and necessary personal details. By completing this step, you obtain an API Key in order to do actions on-behalf-of the user (such as finalizing the onboarding and then creating monetary accounts, cards, payments etc).

**Endpoint:**

```http
POST /v1/partner-user-provision

{
    "external_uuid": "550e8400-e29b-41d4-a716-446655440000",
    "pointer": {
        "type": "EMAIL",
        "value": "user@example.com",
        "name": "user@example.com"
    },
    "products": ["USER_VERIFIED"]
}
```

**Parameters:**

| Field           | Type   | Required | Description                                                                                       |
| --------------- | ------ | -------- | ------------------------------------------------------------------------------------------------- |
| `external_uuid` | string | Yes      | Unique identifier for this provision request. Must be a valid UUID.                               |
| `pointer`       | object | Yes      | User identification pointer (email, phone, etc.)                                                  |
| `pointer.type`  | string | Yes      | <p>Type of pointer.</p><p>Can either be:</p><p><code>EMAIL</code>, <code>PHONE\_NUMBER</code></p> |
| `pointer.value` | string | Yes      | The actual pointer value (email address, phone number)                                            |
| `pointer.name`  | string | Yes      | Display name for the pointer                                                                      |
| `products`      | array  | Yes      | List of products to provision for the user. In this case, `USER_VERIFIED`                         |

**Response:**

```json
{
    "Response": [
        {
            "Id": {
                "id": 123
            }
        }
    ]
}
```

The response contains the `provision_id` used for monitoring. Save this ID for monitoring the provision status.


# Monitoring User Processing

**Endpoint:**

```http
GET /v1/partner-user-provision/{provision_id}
```

**Response (initial):**

```json
{
    "Response": [
        {
            "PartnerUserProvision": {
                "id": 123,
                "created": "2025-08-13 14:45:18.621550",
                "updated": "2025-08-13 14:45:18.621550",
                "external_uuid": "550e8400-e29b-41d4-a716-446655440000",
                "status": "CREATED",
                "sub_status": "PENDING_PROCESS_USER",
                "action_required": "NONE",
                "products": ["USER_VERIFIED"],
                "label_user": null,
                "oauth_request": null,
                "credential": null
            }
        }
    ]
}
```

**Status Explanation:**

* Status: `CREATED` → Provision is created, processing started
* Sub Status: `PENDING_PROCESS_USER` → User account is being created
* label\_user: `null` → User account not yet created
* oauth\_request: `null` → OAuth setup not yet started

Continue polling this endpoint until the `sub_status` changes.

***

## Alternative: Webhook Notifications instead of Polling

*See Chapter 9: Webhooks for more information.*

**Endpoint:**

```http
POST /v1/user/{user_api_key_id}/notification-filter-url
```

**Request Body:**

```json
{
    "notification_filters": [
        {
            "category": "PARTNER_USER_PROVISION",
            "notification_target": "https://your-webhook-endpoint.com/provision-updates"
        }
    ]
}
```


# Monitoring oAuth Processing

**Endpoint:**

```http
GET /v1/partner-user-provision/{provision_id}
```

**Response - OAuth Processing:**

```json
{
    "Response": [
        {
            "PartnerUserProvision": {
                "id": 123,
                "created": "2025-08-13 14:45:18.621550",
                "updated": "2025-08-13 14:45:22.789012",
                "external_uuid": "550e8400-e29b-41d4-a716-446655440000",
                "status": "CREATED",
                "sub_status": "PENDING_PROCESS_OAUTH_REQUEST",
                "action_required": "NONE",
                "products": ["USER_VERIFIED"],
                "label_user": {
                    "uuid": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
                    "display_name": "User Name",
                    "country": "NL",
                    "avatar": {...},
                    "public_nick_name": "UserNick",
                    "type": "PERSON"
                },
                "oauth_request": null,
                "credential": null
            }
        }
    ]
}
```

**Status Explanation:**

* Status: `CREATED` - Still in creation process
* Sub Status: `PENDING_PROCESS_OAUTH_REQUEST` - Setting up OAuth access
* `oauth_request`: `null` - OAuth setup in progress


# Monitoring Relation Processing

**Endpoint:**

```http
GET /v1/partner-user-provision/{provision_id}
```

**Response - Relation Processing:**

```json
{
    "Response": [
        {
            "PartnerUserProvision": {
                "id": 123,
                "created": "2025-08-13 14:45:18.621550",
                "updated": "2025-08-13 14:45:20.123456",
                "external_uuid": "550e8400-e29b-41d4-a716-446655440000",
                "status": "CREATED",
                "sub_status": "PENDING_PROCESS_RELATION_USER",
                "action_required": "NONE",
                "products": ["USER_VERIFIED"],
                "label_user": {
                    "uuid": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
                    "display_name": "User Name",
                    "country": "NL",
                    "avatar": {
                        "uuid": "avatar-uuid",
                        "image": [...],
                        "anchor_uuid": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
                        "style": "NONE"
                    },
                    "public_nick_name": "UserNick",
                    "type": "PERSON"
                },
                "oauth_request": null,
                "credential": null
            }
        }
    ]
}
```

**Status Explanation:**

* Status: `CREATED` - Still in creation process
* Sub Status: `PENDING_PROCESS_RELATION_USER` - Creating partner-user relationship
* `label_user`: Contains user information - user account has been created
* `oauth_request`: `null` - OAuth setup not yet started

The `label_user` field now contains the created user's information, including their UUID and display details.


# Created Provision

At this step, provisioning the user is finalized when you notice the `sub_status = PENDING_PROCESS_USER_ONBOARDING` . At this stage, the user is required to perform actions to finalize the onboarding. And company can use the next onboarding API to send the KYC data to bunq for validation.

**Endpoint:**

```http
GET /v1/partner-user-provision/{provision_id}
```

**Response - Active Provision:**

```json
{
    "Response": [
        {
            "PartnerUserProvision": {
                "id": 123,
                "created": "2025-08-13 14:45:18.621550",
                "updated": "2025-08-13 14:45:25.345678",
                "external_uuid": "550e8400-e29b-41d4-a716-446655440000",
                "status": "CREATED",
                "sub_status": "PENDING_PROCESS_USER_ONBOARDING",
                "action_required": "USER",
                "products": ["USER_VERIFIED"],
                "label_user": {
                    "uuid": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
                    "display_name": "User Name",
                    "country": "NL",
                    "avatar": {...},
                    "public_nick_name": "UserNick",
                    "type": "PERSON"
                },
                "oauth_request": {
                    "id": 456,
                    "created": "2025-08-13 14:45:24.123456",
                    "updated": "2025-08-13 14:45:24.123456",
                    "status": "ACTIVE",
                    "scope": ["PAYMENT_INFORMATION"],
                    "redirect_url": "https://www.bunq.com/",
                    "authorization_code": "auth_code_123",
                    "client_id": "client_id_456"
                },
                "credential": {
                    "id": 789,
                    "created": "2025-08-13 14:45:24.567890",
                    "updated": "2025-08-13 14:45:24.567890",
                    "status": "ACTIVE",
                    "expiry_time": "2025-08-20 14:45:24.567890",
                    "token_value": "credential_token_abc123def456",
                    "permitted_device": {
                        "description": "Partner API Device",
                        "ip": "192.168.1.100"
                    }
                }
            }
        }
    ]
}
```

**Status Explanation:**

* Status: `ACTIVE` - The provision is fully active and ready to use
* Sub Status: `NONE` - No further processing required
* `oauth_request`: Contains OAuth details for API access
* `credential`: Contains user credential information

At this point, the user account is fully provisioned and you can use the OAuth details to make API calls on behalf of the user.


# Closing a Provision

**Endpoint:**

```http
PUT /v1/partner-user-provision/{provision_id}
```

**Request Body:**

```json
{
    "status": "CANCELLED"
}
```

**Response:**

```json
{
    "Response": [
        {
            "Id": {
                "id": 123
            }
        }
    ]
}
```

**After cancellation:**

```json
{
    "Response": [
        {
            "PartnerUserProvision": {
                "id": 123,
                "external_uuid": "550e8400-e29b-41d4-a716-446655440000",
                "status": "CLOSED",
                "sub_status": "NONE",
                "action_required": "NONE",
                "products": ["USER_VERIFIED"],
                "label_user": {...},
                "oauth_request": {...},
                "credential": null
            }
        }
    ]
}
```

#### Main Status Values

| `CREATED`   | Provision created, processing in progress |
| ----------- | ----------------------------------------- |
| `ACTIVE`    | Provision fully active and operational    |
| `CANCELLED` | Provision cancelled by request            |
| `FAILED`    | Provision failed during processing        |
| `CLOSED`    | Provision closed and no longer active     |

#### Sub Status Values

**Main Status Values**

| `CREATED`   | Provision created, processing in progress |
| ----------- | ----------------------------------------- |
| `ACTIVE`    | Provision fully active and operational    |
| `CANCELLED` | Provision cancelled by request            |
| `FAILED`    | Provision failed during processing        |
| `CLOSED`    | Provision closed and no longer active     |

**Sub Status Values**

| `NONE`                            | Any         | No specific sub-process active         |
| --------------------------------- | ----------- | -------------------------------------- |
| `PENDING_PROCESS_USER`            | `CREATED`   | Creating bunq user account             |
| `PENDING_PROCESS_RELATION_USER`   | `CREATED`   | Establishing partner-user relationship |
| `PENDING_PROCESS_OAUTH_REQUEST`   | `CREATED`   | Setting up OAuth access                |
| `PENDING_PROCESS_USER_ONBOARDING` | `CREATED`   | Processing user onboarding             |
| `PENDING_PROCESS_CLOSURE`         | `CANCELLED` | ​                                      |


# Chapter 3 Onboarding


# Creating a session on behalf of the user

## Step 1: Create SessionServer

Before creating notification filters, you need to establish a `SessionServer` using the `token_value` obtained when the `PartnerUserProvision` becomes active.

**Endpoint:**

```http
POST /v1/session-server
```

**Request Body:**

```json
{ 
    "secret": "<credential.token_value_from_created_provision>" 
}
```

The `token_value` is obtained from the credential object when the `PartnerUserProvision` status becomes `CREATED` and subStatus = `PENDING_PROCESS_USER_ONBOARDING` (as shown in Chapter 2).

**Response:**

```json
{
    "Response": [
        {
            "Id": {
                "id": 2173
            }
        },
        {
            "Token": {
                "id": 2173,
                "created": "2025-08-13 15:53:11.594918",
                "updated": "2025-08-13 15:53:11.594918",
                "token": "f981bd6d26b8328b89e85a774c404bb02e72d18062dab92bb59c9e59a1c5958f"
            }
        },
        {
            "UserApiKey": {
                "id": 1364,
                "created": "2025-08-13 15:53:11.286445",
                "updated": "2025-08-13 15:53:11.286445",
                "requested_by_user": {
                    "UserCompany": {
                        "id": 1360,
                        "display_name": "Surrey Coöperatie",
                        "public_nick_name": "Surrey Coöperatie",
                        "avatar": {...},
                        "session_timeout": 604800
                    }
                },
                "granted_by_user": {
                    "UserPerson": {
                        "id": 1363,
                        "display_name": "New bunqer",
                        "public_nick_name": "New bunqer",
                        "avatar": {...},
                        "session_timeout": 604800
                    }
                }
            }
        }
    ]
}
```

* `Id`: Session ID for authentication
* `Token`: Session token for API calls
* `UserApiKey`: Information about the API key relationship between the partner (`requested_by_user`) and the provisioned user (`granted_by_user`)

Save the session ID for subsequent API calls to the provisioned user's endpoints.


# Creating User Onboarding

**Endpoint:**

```http
POST /v1/user/{user_api_key_id}/user-onboarding
{
    "type": "PARTNER_USER"
}
```

**Response:**

<pre class="language-json"><code class="lang-json"><strong>{
</strong>    "Response": [
        {
            "Id": {
                "id": 260
            }
        }
    ]
}
</code></pre>

Now the provisioned user has an Onboarding model which can be updated to be completed.

## Step 2: Update Onboarding model

The response to this endpoint gives you all the information you have to provide to have a verified and active user. All the fulfillments should be completed before the user is fully verified and can create Monetary Accounts, Cards, Payments etc.

**Endpoint:**

```http
PUT /v1/user/{user_api_key_id}/user-onboarding/{user_onboarding_id}
```

*The* `user_api_key_id` *is obtained from the ID of the* `UserApiKey` *object when creating a session on behalf of the user.*

**Request body:**

```json
{
    "user_status_desired": "ACTIVE",
    "user_verification_status_desired": "VERIFIED"
}
```

**Response:**

```json
{
    "Error": [
        {
            "error_description": "You need to provide additional information in order to perform this action.",
            "error_description_translated": "You need to provide additional information in order to perform this action.",
            "error_additional_parameter": [
                {
                    "AdditionalFulfillmentParameter": {
                        "all_fulfillment": [
                            {
                                "type": "ADDRESS_MAIN",
                                "reason": "",
                                "reason_translated": "",
                                "user_id": 877,
                                "status": null,
                                "time_mandatory": null,
                                "all_status_allowed": [
                                    "FULFILLED"
                                ],
                                "id": null,
                                "created_timestamp": null,
                                "updated_timestamp": null
                            },
                            {
                                "type": "DATE_OF_BIRTH",
                                "reason": "Let's make this account truly yours.",
                                "reason_translated": "Let's make this account truly yours.",
                                "user_id": 877,
                                "status": null,
                                "time_mandatory": null,
                                "all_status_allowed": [
                                    "FULFILLED"
                                ],
                                "id": null,
                                "created_timestamp": null,
                                "updated_timestamp": null
                            },
                            {
                                "type": "EMAIL",
                                "reason": "Get notified instantly on important matters.",
                                "reason_translated": "Get notified instantly on important matters.",
                                "user_id": 877,
                                "status": null,
                                "time_mandatory": null,
                                "all_status_allowed": [
                                    "FULFILLED",
                                    "IN_PROGRESS"
                                ],
                                "id": null,
                                "created_timestamp": null,
                                "updated_timestamp": null
                            },
                            {
                                "type": "ADDRESS_MAIN",
                                "reason": "",
                                "reason_translated": "",
                                "user_id": 877,
                                "status": null,
                                "time_mandatory": null,
                                "all_status_allowed": [
                                    "FULFILLED"
                                ],
                                "id": null,
                                "created_timestamp": null,
                                "updated_timestamp": null
                            },
                            {
                                "type": "ADDRESS_POSTAL",
                                "reason": "So we know where to send your card.",
                                "reason_translated": "So we know where to send your card.",
                                "user_id": 877,
                                "status": null,
                                "time_mandatory": null,
                                "all_status_allowed": [
                                    "FULFILLED"
                                ],
                                "id": null,
                                "created_timestamp": null,
                                "updated_timestamp": null
                            },
                            {
                                "type": "IDENTIFICATION_VERIFICATION",
                                "reason": "It takes just a few seconds to take a picture and complete your account.",
                                "reason_translated": "It takes just a few seconds to take a picture and complete your account.",
                                "user_id": 877,
                                "status": null,
                                "time_mandatory": null,
                                "all_status_allowed": [
                                    "FULFILLED",
                                    "IN_PROGRESS"
                                ],
                                "id": null,
                                "created_timestamp": null,
                                "updated_timestamp": null
                            }
                        ],
                        "all_fulfillment_fulfilled": [
                            {
                                "type": "PHONE_NUMBER",
                                "reason": "Secure your account with a phone number. You’ll use this to login.",
                                "reason_translated": "Secure your account with a phone number. You’ll use this to login.",
                                "user_id": 877,
                                "status": "FULFILLED",
                                "time_mandatory": null,
                                "all_status_allowed": [
                                    "FULFILLED"
                                ],
                                "id": 3573,
                                "created_timestamp": null,
                                "updated_timestamp": null
                            }
                        ]
                    }
                }
            ]
        }
    ]
}
```

The Response body below is an example. For the `user_verification_status_desired` that was used in the POST call, the system will respond with the fulfillments that are required to be completed before the user is fully verified.


# Complete Identity Verification with SDK

**Endpoint:**

```http
GET /v1/user/{user_api_key_id}/identity-verification-session/36
```

**Response:**

```json
{
    "Response": [
        {
            "IdentityVerificationSessionIncode": {
                "id": 36,
                "created": "2025-10-31 16:38:53.872547",
                "updated": "2025-10-31 16:38:53.872547",
                "purpose": "VERIFICATION",
                "applicant_identifier_external": null,
                "session_identifier_external": "incode_token",
                "country": "NL",
                "status": "CREATED",
                "token": "token_value_123",
                "url_api": "https://demo-api.incodesmile.com/0/",
                "should_record_session": true,
                "result": null
            }
        }
    ]
}
```

## Step 3: Pass the information to the SDK

Here, the token should be passed to the SDK in order for the session to start. User will then interact with it and they will apply facial verification and ID check. After it is done, your app will obtain a confirmation from the SDK, then you need to inform bunq using the following endpoint.

**Endpoint:**

```html
PUT /v1/user/{user_api_key_id}/identity-verification-session/36
```

**Request Body:**

```json
{
    "status": "SUBMITTED"
}
```

**Response:**

```json
{
    "Response": [
        {
            "Id": {
                "id": 36
            }
        }
    ]
}
```

## Step 4: Keep polling until the result is completed

**Endpoint:**

```http
GET /v1/user/{user_api_key_id}/identity-verification-session/36
```

**Response:**

```json
{
    "Response": [
        {
            "IdentityVerificationSessionIncode": {
                "id": 36,
                "created": "2025-10-31 16:38:53.872547",
                "updated": "2025-10-31 16:38:53.872547",
                "purpose": "VERIFICATION",
                "applicant_identifier_external": null,
                "session_identifier_external": "incode_token",
                "country": "NL",
                "status": "PROCESSED",
                "token": "token_value_123",
                "url_api": "https://demo-api.incodesmile.com/0/",
                "should_record_session": true,
                "result": "APPROVED"
            }
        }
    ]
}
```

Now, user identity verification is completed and user is Approved, you can continue with finalizing the rest of the fulfillments.


# Complete the rest of the fulfilments

TBD — requires Onboarding to be agreed upon.[<br>](broken://spaces/DAjxE0NLYI3ib2scBvPQ/pages/docx9pwt7rvEACyx4trK)


# Chapter 4 Webhooks / Callbacks

On top of our regular [Callbacks (Webhooks)](/basics/callbacks-webhooks) documentation there are some special options for User Provision users.

**NotificationUrl Object:**

| `target_url` | Your configured webhook endpoint URL                     |
| ------------ | -------------------------------------------------------- |
| `category`   | Notification category (e.g., `USER_INFORMATION_INQUIRY`) |
| `event_type` | Specific event type within the category                  |
| `object`     | The actual object that triggered the notification        |

**UserInformationInquiry Object:**

| `id`                     | Unique identifier for the inquiry                                  |
| ------------------------ | ------------------------------------------------------------------ |
| `created`                | Timestamp when the inquiry was created                             |
| `updated`                | Timestamp when the inquiry was last updated                        |
| `user_id`                | ID of the user for whom the inquiry was created                    |
| `title`                  | Title of the inquiry (e.g., "We need additional information")      |
| `subtitle`               | Subtitle explaining the inquiry purpose                            |
| `purpose`                | Purpose of the inquiry (e.g., `COMPLIANCE_TRANSACTION_MONITORING`) |
| `all_entry`              | Array of inquiry entries with specific information requests        |
| `assistant_conversation` | Conversation details for user interaction                          |

**UserInformationInquiryEntry Object:**

| `id`                     | Unique identifier for the inquiry entry                            |
| ------------------------ | ------------------------------------------------------------------ |
| `type`                   | Type of information requested (e.g., `USER_PERSON_SOURCE_OF_FUND`) |
| `status`                 | Status of the entry (e.g., `PENDING`)                              |
| `all_data_submitted`     | Array of data submitted by the user                                |
| `assistant_conversation` | Conversation context for this specific entry                       |
| `context`                | Additional context information                                     |
| `reject_reason`          | Reason for rejection (if applicable)                               |

**Webhook Body Example (`PARTNER_USER_PROVISION`):**

```json
{
  "NotificationUrl": {
    "target_url": "https://webhook.site/73fef6b5-f7ba-419b-b65f-f66199489903",
    "category": "PARTNER_USER_PROVISION",
    "event_type": "PARTNER_USER_PROVISION_PROCESS_UPDATED",
    "object": {
      "PartnerUserProvision": {
        "id": 7,
        "created": "2025-09-03 08:24:13.795007",
        "updated": "2025-09-03 08:24:14.266407",
        "external_uuid": "f8b69bdd-e2fa-4161-9ff7-f4c597f8be8e",
        "status": "ACTIVE",
        "sub_status": "NONE",
        "action_required": "NONE",
        "products": [
          "USER_ANONYMOUS_TRANSACTION_AUDIT"
        ],
        "label_user": {
          "uuid": "ae92e7da-59d6-4dcd-a99b-0109baa5aebc",
          "display_name": "New bunqer",
          "country": "NL",
          "avatar": {
            "uuid": "353e5068-1254-4422-b949-596b9f86ebeb",
            "image": [
              {
                "attachment_public_uuid": "229705c3-9d5c-424f-abc4-3f84559abd6f",
                "height": 640,
                "width": 640,
                "content_type": "image/png",
                "urls": [
                  {
                    "type": "ORIGINAL",
                    "url": "https://bunq-triage-model-storage-public.s3.eu-central-1.amazonaws.com/bunq_file/File/content/e627dab1418debb69a8ab983e7cd99a7b0cb8c289892dde90a8b2f12d5e23140.png"
                  }
                ]
              }
            ],
            "anchor_uuid": "ae92e7da-59d6-4dcd-a99b-0109baa5aebc",
            "style": "NONE"
          },
          "public_nick_name": "New bunqer",
          "type": "PERSON"
        },
        "oauth_request": {
          "uuid": "c59a2b0d-1a16-4a5e-9ad7-93f4921d5b52",
          "created": "2025-09-03 08:24:14.110949",
          "updated": "2025-09-03 08:24:14.182413",
          "oauth_client_id": 13383,
          "oauth_client_display_name": null,
          "response_type": "code",
          "callback_url": "https://yourpartner.com/oauth/callback",
          "status": "ACCEPTED",
          "state": null,
          "authorization_code": "b967643d4c76956b550553a0ebd94f1024c734d0ebaefdca00d42d1f1bfae274",
          "user_alias_created": {
            "uuid": "490f019f-6e1c-4891-b2de-da9e85a12644",
            "display_name": "Sutton Onderlinge Waarborgmaatschappij",
            "country": "000",
            "avatar": {
              "uuid": "9debf55b-70b1-4d43-8d51-d6e7bd42fb5b",
              "image": [
                {
                  "attachment_public_uuid": "4b7e0d1d-9167-48ac-990a-70e342c87812",
                  "height": 126,
                  "width": 200,
                  "content_type": "image/jpeg",
                  "urls": [
                    {
                      "type": "ORIGINAL",
                      "url": "https://bunq-triage-model-storage-public.s3.eu-central-1.amazonaws.com/bunq_file/File/content/6979a145b7ea9ecc3459358122cb560608f02d36d4b8cd6b770f50e36aa35512.jpg"
                    }
                  ]
                }
              ],
              "anchor_uuid": "490f019f-6e1c-4891-b2de-da9e85a12644",
              "style": "NONE"
            },
            "public_nick_name": "Sutton Onderlinge Waarborgmaatschappij",
            "type": "COMPANY"
          },
          "redirect_url": "https://yourpartner.com/oauth/callback?code=b967643d4c76956b550553a0ebd94f1024c734d0ebaefdca00d42d1f1bfae274",
          "type": "BUNQ"
        },
        "credential": {
          "id": 2412203,
          "created": "2025-09-03 08:24:14.178726",
          "updated": "2025-09-03 08:24:14.178726",
          "status": "PENDING_FIRST_USE",
          "expiry_time": "2025-09-03 09:24:14.178685",
          "token_value": "a0239a79b1a8d4429062bbcbdccf30cfda06eb6a2a8effc041085135e36d63d4",
          "permitted_device": null
        }
      }
    }
  }
}
```

#### Callback categories <a href="#callback-categories" id="callback-categories"></a>

Category

Description

BILLING

notifications for all bunq invoices

CARD\_TRANSACTION\_SUCCESSFUL

notifications for successful card transactions

CARD\_TRANSACTION\_FAILED

notifications for failed card transaction

CHAT

notifications for received chat messages

DRAFT\_PAYMENT

notifications for creation and updates of draft payments

IDEAL

notifications for iDEAL-deposits towards a bunq account

SOFORT

notifications for SOFORT-deposits towards a bunq account

MUTATION

notifications for any action that affects a monetary account’s balance

OAUTH

notifications for revoked OAuth connections

PAYMENT

notifications for payments created from, or received on a bunq account (doesn’t include payments that result out of paying a Request, iDEAL, Sofort or Invoice). Outgoing payments have a negative value while incoming payments have a positive value

REQUEST

notifications for incoming requests and updates on outgoing requests

SCHEDULE\_RESULT

notifications for when a scheduled payment is executed

SCHEDULE\_STATUS

notifications about the status of a scheduled payment, e.g. when the scheduled payment is updated or cancelled

SHARE

notifications for any updates or creation of Connects (ShareInviteBankInquiry)

TAB\_RESULT

notifications for updates on Tab payments

BUNQME\_TAB

notifications for updates on bunq.me Tab (open request) payments

SUPPORT

notifications for messages received from us through support chat

#### Mutation Category <a href="#mutation-category" id="mutation-category"></a>

A *Mutation* is a change in the balance of a monetary account. A *Mutation* is created for each payment-like object, such as a request, iDEAL-payment or a regular payment. Therefore, the `MUTATION`category can be used to keep track of a monetary account's balance change.

#### Receiving Callbacks <a href="#receiving-callbacks" id="receiving-callbacks"></a>

* Callbacks for the sandbox environment will be made from different IP's at AWS.
* Callbacks for the production environment will be made from 185.40.108.0/22.

*The IP addresses might change*. We will notify you in a timely fashion if such a change is planned.

#### Removing callbacks <a href="#removing-callbacks" id="removing-callbacks"></a>

To remove callbacks for an object, send a POST request to the `notification_filters` endpoint with a JSON request body with an emtpy list.

```
{
    "notification_filters": []
}
```

### Retry Mechanisms <a href="#retry-mechanisms" id="retry-mechanisms"></a>

When the execution of a callback fails (e.g. the callback server is down or the response contains an error), we try to resend it for a maximum of 5 times, with an interval of one minute between each try. If your server is not reachable by the callback after the 6th total try, the callback is not sent anymore.

#### Listing of failed callbacks <a href="#listing-of-failed-callbacks" id="listing-of-failed-callbacks"></a>

After the sixth attempt of callback executing, the failed entry is stored and can be listed by UserApiKey

```
GET
/v1/user/762/notification-filter-failure
{
    "Response": [
        {
            "NotificationFilterFailure": {
                "id": 1,
                "created": "2023-05-22 06:47:22.043906",
                "updated": "2023-05-22 06:47:22.043906",
                "category": "MUTATION",
                "event_type": "MUTATION_CREATED",
                "object_id": 1278,
                "notification_filters": [
                    {
                        "notification_delivery_method": "URL",
                        "notification_target": "https://coolbank.com/notification",
                        "category": "MUTATION"
                    },
                    {
                        "notification_delivery_method": "URL",
                        "notification_target": "https://coolbank.com/notification",
                        "category": "CARD_TRANSACTION_SUCCESSFUL"
                    }
                ]
            }
        }
    ],
    "Pagination": {
        "future_url": "/v1/user/762/notification-filter-failure?newer_id=1",
        "newer_url": null,
        "older_url": null
    }
}
```

\* the category & object\_id can be used to verify if the callback has failed and should be retried

\*\* the id of the NotificationFilterFailure object should be used to trigger the retry

#### Retry of failed callbacks <a href="#retry-of-failed-callbacks" id="retry-of-failed-callbacks"></a>

Copy

```
POST
/v1/user/762/notification-filter-failure
{
    "notification_filter_failed_ids": "1"
}
```

\* multiple ids can be given in the same field, comma separated. Maximum of 100 ids are allowed

\*\* response will be empty with code 200 (OK)

#### Certificate Pinning <a href="#certificate-pinning" id="certificate-pinning"></a>

We recommend that you use certificate pinning as an extra security measure. We will check if the certificate of the recipient server matches the pinned certificate that you provided and cancel the callback if the check fails or we detect a mismatch.

#### How to set up certificate pinning <a href="#how-to-set-up-certificate-pinning" id="how-to-set-up-certificate-pinning"></a>

1. Retrieve the SSL certificate of your server using the following command:

   `openssl s_client -servername www.example.com -connect www.example.com:443 < /dev/null | sed -n "/-----BEGIN/,/-----END/p" > www.example.com.pem`
2. `POST` the certificate to the `certificate-pinned`endpoint.

Once ready, every callback will be checked against the pinned certificate that you provided. Note that if the SSL certificate on your server expires or is changed, our callbacks will fail.


# Getting Started with Banking As A Service


# Introduction & Core Paradigms

Before you make your first API call, it's worth spending a few minutes understanding how bunq thinks about banking. The API is consistent and predictable — but only once the underlying model clicks.

***

### bunq is the bank. You are the platform.

When you integrate bunq BaaS, you're not connecting to a ledger or a payment processor. You're connecting to a licensed bank. That distinction matters in two ways:

1. **Regulation is handled for you.** bunq holds the banking license, manages AML obligations, safeguards funds, and sits on the payment rails. You don't need your own EMI license to issue IBANs or cards through bunq.
2. **You stay in control.** Everything bunq does for your users — opening accounts, issuing cards, verifying identity, moving money — is driven by API calls your platform makes. bunq executes; you orchestrate.

***

### The user hierarchy

The most important concept in the bunq data model is the **user hierarchy**.

```
bunq (the bank)
└── Your platform (API key holder)
    └── Sub-user A  ←  KYC-verified individual
    │   ├── Monetary Account (IBAN)
    │   │   └── Payments
    │   └── Card
    └── Sub-user B
        ├── Monetary Account
        └── Card
```

Everything in bunq lives under a `user`. When you use BaaS, you create and manage **sub-users** — one per end-user on your platform. Each sub-user goes through KYC, gets their own monetary accounts, and can have cards issued against those accounts.

Your API credentials sit at the top of this tree. You act on behalf of sub-users using the user ID in your API paths: `/v1/user/{userID}/monetary-account`, `/v1/user/{userID}/payment`, and so on.

***

### Everything is an object

bunq's API is resource-oriented. The key objects you'll work with in BaaS, and how they relate:

| Object             | What it is                              | Lives under                             |
| ------------------ | --------------------------------------- | --------------------------------------- |
| `User`             | A verified identity (your end-user)     | Your platform credentials               |
| `MonetaryAccount`  | A bank account with an IBAN             | A `User`                                |
| `Payment`          | A money movement (in or out)            | A `MonetaryAccount`                     |
| `Card`             | A Mastercard debit card                 | A `User`, linked to a `MonetaryAccount` |
| `MastercardAction` | A card transaction (auth or settlement) | A `MonetaryAccount`                     |

Objects reference each other by ID. A card points to its linked monetary account. A payment points to the account it moved money on. When something goes wrong or you need to reconcile, you can always trace the chain back.

***

### The session model

bunq uses a **signed-request, session-token** authentication pattern. You set it up once per integration, then manage short-lived sessions.

The flow has three steps:

1. **Installation** — register your public key with bunq via `POST /v1/installation`. Receive an installation token.
2. **Device registration** — identify your server via `POST /v1/device-server` using the installation token.
3. **Session** — start a session via `POST /v1/session-server`. Receive a session token valid for the duration of that session.

Use the session token in the `X-Bunq-Client-Authentication` header on every subsequent request. Sessions expire after inactivity — your integration should handle re-authentication gracefully.

Every request must also be **signed** with your private key and include the signature in the `X-Bunq-Client-Signature` header. bunq verifies this signature server-side, and signs its responses so you can verify them too.

For the full authentication walkthrough, see [Basics → Getting Started](https://doc.bunq.com/basics).

***

### The lifecycle pattern

Most objects in bunq move through a predictable lifecycle: created → active → closed. The key states to handle in your integration:

| Object          | States to handle                                            |
| --------------- | ----------------------------------------------------------- |
| Sub-user        | `PENDING` → `VERIFIED` (KYC complete) / `REJECTED`          |
| MonetaryAccount | `PENDING` → `ACTIVE` → `CANCELLED`                          |
| Payment         | `PENDING` → `EXECUTED` / `REJECTED` / `REVERTED`            |
| Card            | `ACTIVE` → `SUSPENDED` (frozen) → `DEACTIVATED` / `EXPIRED` |

Design your platform logic around these states. Don't assume an object is active just because you created it — always check status before acting on it.

***

### Callbacks, not polling

bunq is event-driven. Instead of polling for changes, register a **notification filter** (webhook) on the user or account you care about. bunq will `POST` to your endpoint when something happens — a payment arrives, a KYC result comes in, a card transaction is authorised.

```
POST /v1/user/{userID}/notification-filter-url
{
  "notification_filters": [
    {
      "notification_delivery_method": "URL",
      "notification_target": "https://your-platform.com/webhooks/bunq",
      "category": "PAYMENT"
    }
  ]
}
```

Always verify the `X-Bunq-Server-Signature` header on incoming callbacks before processing them. Respond with a `200` quickly — do your processing asynchronously.

Available callback categories relevant to BaaS: `PAYMENT`, `MUTATION`, `CARD_TRANSACTION_AUTHORISED`, `CARD_TRANSACTION_DECLINED`, `KYC`.

***

### Idempotency

Network failures happen. For any state-changing request (creating a payment, issuing a card), always include a unique `X-Bunq-Client-Request-Id` header. If you retry a failed request with the same ID, bunq will return the original result rather than executing the action twice.

```
X-Bunq-Client-Request-Id: your-unique-uuid-here
```

Generate a fresh UUID per request and store it alongside the pending operation. If the request times out, retry with the same ID. Once you have a confirmed response, the ID can be retired.

***

### Sandbox vs production

bunq provides a full sandbox environment at `public-api.sandbox.bunq.com`. It mirrors the production API exactly — same endpoints, same object model, same authentication flow — but uses test money and synthetic identity verification.

Build and test your entire integration in sandbox before going live. The only things that differ in production are real IBANs, real card networks, and real KYC checks.

|          | Sandbox                        | Production                      |
| -------- | ------------------------------ | ------------------------------- |
| Base URL | `public-api.sandbox.bunq.com`  | `api.bunq.com`                  |
| API key  | Generated via sandbox tools    | Issued by bunq after onboarding |
| KYC      | Synthetic (always passes)      | Real identity verification      |
| Payments | Test money, no real settlement | Live SEPA rails                 |
| Cards    | Virtual test cards             | Real Mastercard-network cards   |

***

### What to build first

If you're starting a new BaaS integration, the recommended sequence is:

1. **Authentication** — get your sandbox credentials, complete the installation → device → session flow.
2. **KYC** — create a test sub-user and verify them through the sandbox KYC flow.
3. **Monetary account** — open an account for the verified user and note the IBAN.
4. **Payments** — send a test payment out; trigger an incoming payment to the IBAN.
5. **Cards** — issue a virtual card and generate a CVC2.
6. **Callbacks** — register a webhook and verify you're receiving events for each of the above.

Once all six work end-to-end in sandbox, you're ready to go through the production onboarding process with your bunq BaaS account manager.


# Overview

## Banking as a Service

bunq is a fully licensed European bank — and we're opening the entire stack to developers and businesses who want to build financial products without starting from scratch.

With bunq's Banking as a Service (BaaS) APIs, you get direct access to the same core infrastructure that powers millions of bunq accounts: real IBANs, payment rails, card issuing, and identity verification. No middleware, no workarounds — just clean REST APIs backed by a real bank.

{% hint style="info" %}
BaaS APIs are available to approved partners. [Get in touch](mailto:sales@bunq.com) to discuss access.
{% endhint %}

***

### What you can build

Whether you're embedding financial services into an existing product or building a fintech from the ground up, bunq BaaS gives you the building blocks to do it:

* **KYC as a Service** — verify your users' identities using bunq's regulated onboarding flow, without building your own compliance infrastructure.
* **Monetary Accounts** — open and manage euro bank accounts (with real IBANs) on behalf of your users, programmatically.
* **Payments** — initiate SEPA credit transfers, handle incoming payments, and manage payment flows end-to-end.
* **Cards** — issue and manage virtual and physical Mastercard debit cards linked to bunq accounts.

***

### How it works

bunq BaaS is built on the same public bunq API that powers our consumer and business products. It uses standard REST over HTTPS, with signed requests for security.

The key paradigm to understand is the **user hierarchy**: every account, card, and payment lives under a `user` object. When you use BaaS, you act on behalf of sub-users that your platform creates and manages — giving you full programmatic control while bunq handles the regulatory and banking layer underneath.

Authentication follows the same installation → session pattern used across the bunq API. You'll set up your API credentials once, then use session tokens for all subsequent calls.

For the full authentication flow, see [Basics → Getting Started](https://doc.bunq.com/basics).

***

### BaaS vs the standard bunq API

|                    | Standard bunq API                    | BaaS                                            |
| ------------------ | ------------------------------------ | ----------------------------------------------- |
| Use case           | Automate your own bunq account       | Build white label financial products for others |
| User model         | Your own bunq user                   | Sub-users managed by your platform              |
| KYC                | bunq handles it for your account     | You trigger it via API for your users           |
| Card issuing       | Manage your own cards                | Issue cards on behalf of sub-users              |
| Typical integrator | Developer, fintech, accountancy tool | Neobank, SaaS platform, marketplace             |

***

### Next steps

Pick the capability you want to integrate and follow the dedicated guide:

{% content-ref url="/pages/w1uOfaXHLlimhRKx2d36" %}
[Bank Accounts - As a Service](/banking-as-a-service/bank-accounts-as-a-service)
{% endcontent-ref %}

{% content-ref url="/pages/eiSPRVVyVUwjsvhGKt0A" %}
[Cards - As a Service](/banking-as-a-service/cards-as-a-service)
{% endcontent-ref %}

{% content-ref url="/pages/axDXpXkhimhuhCDgGEyn" %}
[Payments - As a Service](/banking-as-a-service/payments-as-a-service)
{% endcontent-ref %}

{% content-ref url="/pages/uNLOGz6ZH2eq7e0gb9wf" %}
[Know Your Customer - As a Service](/banking-as-a-service/know-your-customer-as-a-service)
{% endcontent-ref %}


# Cards - As a Service

## Cards

bunq's card issuing API lets you create and manage Mastercard debit cards linked directly to your users' monetary accounts. Cards work everywhere Mastercard is accepted — online, in-store, and in digital wallets.

***

### The paradigm

In bunq, every card is linked to a monetary account. Spending on the card debits that account in real time. You control the card lifecycle — from creation and PIN management to spending limits, freezing, and cancellation — entirely through the API.

bunq issues two card types:

* **Virtual cards** — available immediately after creation, delivered as a card number, expiry, and CVC. Ideal for online purchases, subscriptions, and digital wallet provisioning.
* **Physical cards** — shipped to the cardholder's address. Same API creation flow; delivery typically takes 5–7 business days within the EU.

Key things to understand:

* **One card, one account.** A card is always linked to exactly one monetary account. Changing the linked account is supported — useful if you want to redirect spending to a different balance.
* **Spending limits.** You can set daily, weekly, or monthly spending limits per card via the API. You can also restrict the card to specific MCC (merchant category) codes.
* **CVC2 generation.** For virtual cards, you generate a fresh CVC2 on demand via API. CVC2s are valid for a short window and regenerated each time — there is no stored static CVC.
* **Freezing and unfreezing.** Cards can be frozen instantly via API (e.g. if a user reports it missing) and unfrozen just as quickly.
* **Callbacks.** Register a notification filter to receive real-time events for authorisations, settlements, and declines.

***

### Flow overview

**Issuing a virtual card:**

```
Your platform                    bunq API
─────────────                    ────────
POST /card-virtual           →   Creates card, returns PAN + expiry
POST /generated-cvc2         →   Returns a fresh CVC2 for the card
                             ←   Card ready for use
```

**Handling a transaction:**

```
Cardholder makes a purchase       bunq API         Your platform
──────────────────────────        ────────         ─────────────
Card presented at merchant    →   Authorisation
                              →   Callback: CARD_TRANSACTION_AUTHORISED → Your webhook
Settlement (T+1)              →   Payment object created on account
```

***

### Relevant API endpoints

| Action                                     | Endpoint                                                                                                                       |
| ------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------ |
| Issue a virtual card                       | [`POST /v1/user/{userID}/card-virtual`](https://doc.bunq.com/api-reference/cards)                                              |
| List cards for a user                      | [`GET /v1/user/{userID}/card`](https://doc.bunq.com/api-reference/cards)                                                       |
| Get card details                           | [`GET /v1/user/{userID}/card/{itemID}`](https://doc.bunq.com/api-reference/cards)                                              |
| Update card (limits, freeze, link account) | [`PUT /v1/user/{userID}/card/{itemID}`](https://doc.bunq.com/api-reference/cards)                                              |
| Generate a CVC2                            | [`POST /v1/user/{userID}/card/{cardID}/generated-cvc2`](https://doc.bunq.com/api-reference/generated-cvc2)                     |
| List card transactions                     | [`GET /v1/user/{userID}/monetary-account/{accountID}/mastercard-action`](https://doc.bunq.com/api-reference/mastercard-action) |
| Register card callbacks                    | [`POST /v1/user/{userID}/notification-filter-url`](https://doc.bunq.com/api-reference/notification-filter-url)                 |

***

### Card object highlights

The card object returned after creation includes:

* `id` — your persistent reference for this card
* `pan_link` — a link to the card's full PAN (fetched separately for PCI compliance reasons)
* `expiry_date` — in `YYYY-MM` format
* `status` — `ACTIVE`, `DEACTIVATED`, `SUSPENDED`, `EXPIRED`
* `limit` — array of spending limit objects, each with `value`, `currency`, and `type` (daily/weekly/monthly)
* `primary_account_number_four_digit` — last four digits, safe to display in your UI

***

### Common integration patterns

**Pattern 1 — Instant virtual card per user.** Issue a virtual card at the end of KYC + account creation. The user gets a card they can add to Apple Pay / Google Pay immediately, without waiting for a physical card.

**Pattern 2 — Expense cards with limits.** Issue cards to employees or contractors with a fixed monthly spending limit and MCC restrictions. Adjust limits dynamically as budgets change — no need to reissue the card.

**Pattern 3 — Disposable cards.** Issue a virtual card for a single transaction or campaign, then cancel it. Eliminates the risk of recurring merchant charges or data exposure from a compromised PAN.

**Pattern 4 — Freeze on suspicious activity.** Use your own fraud signals to freeze a card instantly via `PUT /card/{id}` with `status: SUSPENDED`. Unfreeze just as quickly once verified. Pair with transaction callbacks to act in near-real-time.

***

{% hint style="info" %}
Physical card orders require a verified postal address on the sub-user's profile. Make sure the user's address is set before calling the physical card creation endpoint.
{% endhint %}

{% hint style="info" %}
CVC2 values must not be stored. Generate them on demand and display them directly to the cardholder. Storing CVC2s violates PCI DSS requirements.
{% endhint %}


# Payments - As a Service

## Payments

Once a user has a verified monetary account, they can send and receive money on the SEPA network. bunq's payments API gives you full programmatic control over both sides of that flow.

***

### The paradigm

Payments in bunq are always scoped to a monetary account. To initiate a payment, you specify the source account, the destination (an IBAN or bunq alias), the amount, and a description. bunq handles the routing — internal transfers between bunq accounts settle instantly; external SEPA payments follow standard rail timelines.

Incoming payments arrive on the account's IBAN. Your integration receives them via callbacks and can query the transaction history at any time.

Key things to understand:

* **Payment types.** The `Payment` object covers both outgoing and incoming transfers. Filter by `type` (`IDEAL`, `SEPA`, `INTERNAL`, etc.) to distinguish them.
* **Draft payments.** For flows that require a second approval step (e.g. a human signs off before money moves), use `DraftPayment`. The payment is created but not executed until explicitly confirmed.
* **Batch payments.** You can send multiple payments in a single API call using `PaymentBatch`. Useful for payroll runs, mass payouts, or invoice settlements.
* **Idempotency.** Use the `X-Bunq-Client-Request-Id` header to safely retry failed requests without risk of double payments.
* **Callbacks.** Register a notification filter on the account to receive a webhook whenever a payment arrives or a payment status changes.

***

### Flow overview

**Outgoing payment:**

```
Your platform                         bunq API
─────────────                         ────────
POST /payment                    →    Validates and queues payment
                                 ←    Payment object (status: PENDING)
                                 ←    Callback: PAYMENT_CREATED / PAYMENT_EXECUTED
```

**Incoming payment:**

```
External sender                       bunq API          Your platform
───────────────                       ────────          ─────────────
Sends SEPA to user's IBAN        →    Credits account
                                 →    Callback: MUTATION →  Your webhook
GET /payment/{id}                ←    Full transaction detail
```

***

### Relevant API endpoints

| Action                      | Endpoint                                                                                                                |
| --------------------------- | ----------------------------------------------------------------------------------------------------------------------- |
| Send a payment              | [`POST /v1/user/{userID}/monetary-account/{accountID}/payment`](https://doc.bunq.com/api-reference/payment)             |
| List payments (history)     | [`GET /v1/user/{userID}/monetary-account/{accountID}/payment`](https://doc.bunq.com/api-reference/payment)              |
| Get a specific payment      | [`GET /v1/user/{userID}/monetary-account/{accountID}/payment/{itemID}`](https://doc.bunq.com/api-reference/payment)     |
| Create a draft payment      | [`POST /v1/user/{userID}/monetary-account/{accountID}/draft-payment`](https://doc.bunq.com/api-reference/draft-payment) |
| Send a batch of payments    | [`POST /v1/user/{userID}/monetary-account/{accountID}/payment-batch`](https://doc.bunq.com/api-reference/payment-batch) |
| Register a payment callback | [`POST /v1/user/{userID}/notification-filter-url`](https://doc.bunq.com/api-reference/notification-filter-url)          |

***

### Payment object highlights

The payment response includes:

* `amount` — object with `value` (string decimal) and `currency`
* `counterparty_alias` — the other party's IBAN or bunq alias
* `description` — the payment reference (appears on the recipient's statement)
* `type` — payment rail used (`SEPA_CREDIT_TRANSFER`, `INTERNAL`, etc.)
* `status` — current state (`PENDING`, `EXECUTED`, `REJECTED`, `REVERTED`)
* `created` / `updated` — timestamps in `YYYY-MM-DD HH:MM:SS.SSSSSS` format

***

### Common integration patterns

**Pattern 1 — Instant payouts.** Trigger a payment immediately when an event occurs in your platform (e.g. a gig worker completes a task). Use callbacks to confirm execution and update your own records.

**Pattern 2 — Scheduled / batch payroll.** Collect payment instructions throughout the week, then fire a `PaymentBatch` on payday. Simpler to manage than individual calls and easier to reconcile.

**Pattern 3 — Incoming payment reconciliation.** Each account has a unique IBAN. Assign one account per customer or invoice, so incoming payments self-identify by destination. Query the payment's `description` field for additional reference matching.

**Pattern 4 — Two-step approval.** Use `DraftPayment` for high-value transactions that require a human review step before execution. Your platform surfaces the draft, an authorised user confirms it, and you send the confirmation via API.

***

{% hint style="warning" %} Always store the `X-Bunq-Client-Request-Id` you send with each payment request. If a request times out, resubmit the same ID — bunq will deduplicate and return the original result rather than creating a second payment. {% endhint %}


# Bank Accounts - As a Service

## Monetary Accounts

A monetary account is the core object in the bunq data model. It's a real euro bank account with its own IBAN, capable of sending and receiving SEPA payments, holding a balance, and being linked to one or more cards.

With bunq BaaS, you can open monetary accounts programmatically on behalf of your verified sub-users. Each account is a fully functional bank account — not a virtual ledger or a pre-paid balance, but a real account at a regulated Dutch bank.

***

### The paradigm

Every monetary account belongs to a `user`. Before you can create an account for someone, that user must be KYC-verified (see [KYC as a Service](https://doc.bunq.com/banking-as-a-service/know-your-customer-as-a-service)).

A single user can hold multiple monetary accounts — for example, a main account and a savings pot, or accounts in different currencies. Each account has its own IBAN and tracks its own transaction history.

The main account type you'll use for BaaS is `MonetaryAccountBank` — a standard IBAN-bearing euro account.

Key things to understand:

* **IBANs are real.** Every `MonetaryAccountBank` gets a dedicated IBAN that works on all SEPA payment rails. Third parties can send money directly to your user's IBAN.
* **Account status.** Accounts move through `PENDING`, `ACTIVE`, `CANCELLED`, and `CANCELLED_PENDING_SETTLEMENT` states. Most of your logic will act on `ACTIVE` accounts.
* **Balance and transactions.** The balance and full transaction history are queryable at any time via the account ID.
* **Limits.** Account limits (transaction volume, balance caps) are set at the BaaS agreement level. Contact your account manager to adjust them.

***

### Flow overview

```
Your platform                    bunq API
─────────────                    ────────
(KYC verified sub-user exists)
POST /monetary-account-bank  →   Opens account, returns IBAN
GET  /monetary-account/{id}  →   Fetch balance and status
GET  /payment                →   List transactions on account
```

***

### Relevant API endpoints

| Action                       | Endpoint                                                                                                                                            |
| ---------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------- |
| Create a bank account (IBAN) | [`POST /v1/user/{userID}/monetary-account-bank`](https://doc.bunq.com/monetary-account/monetary-account-bank)                                       |
| List all accounts for a user | [`GET /v1/user/{userID}/monetary-account`](https://doc.bunq.com/api-reference/monetary-account)                                                     |
| Get a specific account       | [`GET /v1/user/{userID}/monetary-account-bank/{itemID}`](https://doc.bunq.com/monetary-account/monetary-account-bank)                               |
| Close / cancel an account    | [`PUT /v1/user/{userID}/monetary-account-bank/{itemID}`](https://doc.bunq.com/monetary-account/monetary-account-bank) (set `status` to `CANCELLED`) |
| Get account balance          | Included in the account object — see `balance` field                                                                                                |
| List transactions            | [`GET /v1/user/{userID}/monetary-account/{accountID}/payment`](https://doc.bunq.com/api-reference/payment)                                          |

***

### Account object highlights

When you `GET` a monetary account, the response includes:

* `iban` — the account's full IBAN (share this with your user or third parties for incoming payments)
* `balance` — current balance object with `value` and `currency`
* `status` — account lifecycle state
* `display_name` — human-readable label you set at creation
* `alias` — additional payment aliases (e.g. phone number, email) if configured

***

### Common integration patterns

**Pattern 1 — One account per user.** The simplest model. Create one `MonetaryAccountBank` per verified sub-user. Works well for platforms where each user has a single wallet or balance.

**Pattern 2 — Multiple accounts per user.** Create separate accounts for different purposes (e.g. "spending" vs "savings", or separate accounts per project/invoice). Users and your platform can move money between these accounts using internal transfers.

**Pattern 3 — Shared or pooled accounts.** For marketplaces or escrow-like flows, a single account can receive funds from many parties. Use transaction metadata and callbacks to reconcile incoming payments to the correct source.

***

{% hint style="info" %}
Monetary account creation requires the sub-user to have `VERIFIED` KYC status. Attempting to create an account for an unverified user will return a `400` error.
{% endhint %}


# Know Your Customer - As a Service

## KYC as a Service

Know Your Customer (KYC) is a regulatory requirement for any business that holds money or moves it on behalf of people. Building a compliant onboarding flow from scratch — identity document checks, liveness detection, sanctions screening, PEP lists — is expensive, slow, and requires maintaining your own compliance program.

With bunq's KYC as a Service, you delegate that entire process to a regulated bank. bunq handles the verification, stores the compliance record, and issues a verified user that your platform can immediately act on.

***

### The paradigm

When a new user signs up to your platform, you create a **sub-user** via the bunq API and kick off the KYC flow. bunq guides your user through identity verification (document upload + liveness check). Once verified, that user gets a bunq identity tied to your platform — ready to open accounts, receive payments, and use cards.

You never handle raw identity documents. bunq does the regulated heavy lifting; you get a simple boolean: verified or not.

Key things to understand:

* **Sub-user model.** Each end-user on your platform maps to a bunq sub-user object. KYC status lives on that sub-user.
* **Verification is asynchronous.** After initiating KYC, use a callback URL to receive the result rather than polling.
* **Retries and failure states.** A user can fail verification (e.g. blurry document) and retry. Your integration should handle `PENDING`, `VERIFIED`, and `REJECTED` states.
* **Regulatory scope.** bunq's KYC covers EU individuals. For corporate onboarding (KYB) or non-EU users, contact your bunq BaaS account manager.

***

### Flow overview

```
Your platform          bunq API
─────────────          ────────
POST /user         →   Creates sub-user shell
POST /kyc-request  →   Triggers identity verification flow
                   ←   Callback: KYC_RESULT (VERIFIED / REJECTED)
GET  /user/{id}    →   Fetch verified user object
```

***

### Relevant API endpoints

| Action                        | Endpoint                                                                                                       |
| ----------------------------- | -------------------------------------------------------------------------------------------------------------- |
| Create a sub-user             | [`POST /v1/user`](https://doc.bunq.com/api-reference/user)                                                     |
| Get user details / KYC status | [`GET /v1/user/{userID}`](https://doc.bunq.com/api-reference/user)                                             |
| Set a callback URL for events | [`POST /v1/user/{userID}/notification-filter-url`](https://doc.bunq.com/api-reference/notification-filter-url) |

{% hint style="info" %}
The full KYC initiation endpoint and request schema are available upon request. Contact your BaaS account manager for the specific endpoint path for your integration tier.
{% endhint %}

***

### Callback payload

When verification completes, bunq sends a `POST` to your registered callback URL. The payload includes the user ID, the new KYC status, and a timestamp. Always verify the `X-Bunq-Server-Signature` header before processing.

***

### Common integration patterns

**Pattern 1 — Verify before account creation.** Gate monetary account creation on `VERIFIED` status. This is the safest approach and the most common for financial-product builders.

**Pattern 2 — Optimistic onboarding.** Let users start a limited flow (e.g. receive a payment link) before full KYC, and upgrade their permissions once verified. Requires careful UX design to avoid confusing users about what they can and can't do yet.


# Are you a Third Party Provider (TPP)? Start here!

If you're a PSD2-certified third-party provider (TPP), you can integrate directly with the bunq Public API using your **eIDAS certificate**. This guide walks you through the step-by-step process to register and authenticate with the bunq environment.

{% hint style="danger" %}
PSD2 providers do **not** have access to the bunq Developers Portal. Integration is done entirely through API calls.
{% endhint %}

### 🚀 What Do You Need to Integrate with bunq?

Getting started with the bunq Public API is simple! Here’s what you’ll need to build, test, and simulate your integration from start to finish:

{% hint style="info" %}
If you get stuck or have questions, you can always reach out to [our support team](/support/api-support-team). We're here to help!
{% endhint %}

#### 🧪 Test Your Integration with Postman

Want to explore the API before writing a single line of code? We’ve got you covered.

Our official **Postman collection** lets you:

* Send requests to any bunq endpoint
* Simulate real API behavior
* Inspect responses and debug easily
* Automate tests as you go

Perfect for learning the API or validating your integration logic.

You can find more info here:

{% content-ref url="/pages/N4PEtvluykQpcB57RnKI" %}
[Postman](/getting-started/tools/postman)
{% endcontent-ref %}

#### 📱 Simulate User Behavior with Our APK

Need to see how things work from a user’s perspective? Use our **bunq APK** to simulate real user actions like logging in, approving payments, or accepting requests.

This is especially useful for:

* End-to-end testing
* Mimicking user approvals for DraftPayments or OAuth flows
* Validating UX before going live

Please check here how to use our Android APK:

{% content-ref url="/pages/cTVOKghIW3JAaoiKgWSA" %}
[Android Emulator](/getting-started/tools/android-emulator)
{% endcontent-ref %}


# Register as a TPP

## Register as a TPP

This guide will walk you through the steps to register as a Third-Party Provider (TPP) with bunq. If you're a PSD2-certified company, you'll learn how to authenticate with the bunq API, register your certificate, and start using your credentials to access the data and services you're authorized for. Pre-Requisite You must generate a 2048-bit RSA key pair beforehand.

You'll need your QSeal certificate (Qualified Seal Certificate), including intermediate and root certificate chain.

OpenSSL and curl installed

Basic knowledge of command line

The easiest way to register is via command line, but you can also implement this in your code. If you're in a hurry and just want to test in the Sandbox environment. We bundled all commands on this page into 1 bash script that creates a PSD2 user and registers it for you. You'll just get an API key for that created user that you can use for the rest of your implementation Although a script provides you with a quick way to get started, we do recommend you read this page thorougly before moving into production. Link to script

🛠️ Step-by-Step Integration

1. Generate Installation Key Pair These keys are used to register your app installation with bunq:

## Public key: installation.pub

## Private key: installation.key

openssl genrsa -out installation.key && openssl rsa -in installation.key -outform PEM -pubout -out installation.pub

2. (Sandbox only) Generate a test PSD2 Certificate The certificate is not validated in sandbox, so you can create as many as you need. Just make sure to use the qSEAL certificate when moving to production.

Replace the subject /CN=.../C=... with your own details as needed.

## Certificate: psd2.cert

## Private key: psd2.key

openssl req -x509 -newkey rsa:4096 -keyout psd2.key -out psd2.cert -days 365 -nodes -subj "/CN=Test PISP AISP $(uuidgen)/C=NL"

3. Create Installation This step registers your public key with bunq and returns an installation token. The client\_public\_key is the public part of the key pair you generated earlier using OpenSSL. This key is sent to bunq so we know how to verify future requests from your integration. What the API expects here is: A PEM-formatted public key (typically starting with -----BEGIN PUBLIC KEY----- and ending with -----END PUBLIC KEY-----)

All line breaks and formatting preserved correctly as a single escaped string (so it fits into the JSON payload)

Here's an example of a formatted public key: "-----BEGIN PUBLIC KEY-----\nMIIBITANBgkqhkiG9w0BAQEFAAOCAQ4AMIIBCQKCAQBKvVUm/gMi7NmTQImtpX1K\nTFMy3BQPvi6uYWMIIy/YHlZNGZbseKyo/dSa22VnFitjoJAt1S6iy04iuiYKCo4p\nUT9jNhn+JW7+U5Ptia6Y1yDwAqioeuL90suO6XLk35Vj7uuyXxlZO3u79/nPJrmp\nmYx2kEhEEISVWd9+TAFrFjImdGVd6DXK4d3D8/tH4GwILcmL7PbigbFLjeCVbkUi\nFqSiMgtQJkHVHhwedwLehuNg/oL3MRBw1bIxrYnjpO6qfyWoYNmCKYo3KgZYrQZ8\nVUjD0bpyfZEWX3+c849nemRdDa8eUZqjzneV2P/m96iiLWbve5KKSklSz2UtCecD\nAgMBAAE=\n-----END PUBLIC KEY-----\n"

You can call the installation endpoint by using the following command line: INSTALLATION=$(curl -X POST <https://public-api.sandbox.bunq.com/v1/installation\\>
-H "Accept: application/json"\
-H "Content-Type: application/json"\
-H "X-Bunq-Client-Request-Id: $(uuidgen)"\
\--data "{"client\_public\_key": "$(awk 'NF {sub(/\r/, ""); printf "%s\n", $0;}' installation.pub)"}")

Here is the full specification of the endpoint:

4. Generate the Signature This proves ownership of your PSD2 certificate by signing the public key and token. Make sure there is NO new line at the end of the file! Otherwise, the signature will be invalid.

Extract the installation\_token from the previous step:

TOKEN=$(echo $INSTALLATION | grep -o '"token":"\[A-Za-z0-9]\*"' | cut -d '"' -f 4) echo -n $TOKEN > installation.token

Take the server\_public\_key of the installation you also received in the previous step.

Append the token

Sign the string using the private key of your PSD2 certificate

openssl dgst -sign psd2.key -keyform PEM -sha256 -out signature <(cat installation.pub installation.token)

This base64 string should be passed as value of client\_public\_key\_signature in the next step.

6. Create Payment Service Provider Credential Use your certificate and signature to request your TPP credentials in bunq's API. Here is the command line code: CREDENTIAL=$(curl -X POST <https://public-api.sandbox.bunq.com/v1/payment-service-provider-credential\\>
   -H "Accept: application/json"\
   -H "Content-Type: application/json"\
   -H "X-Bunq-Client-Request-Id: $(uuidgen)"\
   -H "X-Bunq-Client-Authentication: $TOKEN"\
   \--data "{ "client\_payment\_service\_provider\_certificate": "$(awk 'NF {sub(/\r/, ""); printf "%s\n", $0;}' psd2.cert)", "client\_payment\_service\_provider\_certificate\_chain": "$(awk 'NF {sub(/\r/, ""); printf "%s\n", $0;}' psd2.cert)", "client\_public\_key\_signature": "$(cat signature | base64)" }")

You can repeat the same value on the client\_payment\_service\_provider\_certificate and client\_payment\_service\_provider\_certificate\_chain in sandbox, but please ensure you have your certificate chain ready when going to production, otherwise the call will fail.

Save the response for the next step: echo $CREDENTIAL > credential.json

Here is the full specification of the endpoint:

It's good to know that if you register as a Payment Server Provider you will not be a 'typical' USER\_PERSON or USER\_COMPANY ( ). Instead you'll become a UserPaymentServiceProvider - this is a special kind of user that only serves as a user for your OAuth installation. This user type will not have any bank accounts or payments of it's own. You can only manage that user through our API

> **Need to update your PSD2 certificate?** See the dedicated guide: [Rotating your qSEAL Certificate](https://github.com/bunq/gitbook-documentation/tree/master/psd2/are-you-a-third-party-provider-tpp-start-here/rotating-your-qseal-certificate/README.md)

7. Extract Credential Token and register Your Device After creating a credential in the previous step, you'll receive a credential\_token, which acts as a unique secret. This token is required when registering your device using POST /device-server. Registering the device is an important security step—it lets bunq know where the API calls are coming from and links your setup to a specific environment (like your server or app). By sending the credential token as the secret, you're proving that your device is authorized to operate under your PSD2 certificate and credentials. Without this step, bunq can't associate API activity with a verified, trusted source. Here's the bash code to extract your credential\_token from the credential.json (saved in the last step): CREDENTIAL\_TOKEN=$(cat credential.json | grep -o '"token\_value":"\[A-Za-z0-9]\*"' | cut -d '"' -f 4)

With that value in hands, you can then call the endpoint POST /device-server and register your device: curl -X POST <https://public-api.sandbox.bunq.com/v1/device-server\\>
-H "Accept: application/json"\
-H "Content-Type: application/json"\
-H "X-Bunq-Client-Request-Id: $(uuidgen)"\
-H "X-Bunq-Client-Authentication: $TOKEN"\
\--data "{"secret":"$CREDENTIAL\_TOKEN", "description": "My server"}"

IP addresses When using a standard API Key the DeviceServer and Installation that are created in this process are bound to the IP address they are created from. Using a Wildcard API Key gives you the freedom to make API calls from any IP address after the POST device-server. You can switch to a Wildcard API Key by tapping on "Allow All IP Addresses" in your API Key menu inside the bunq app. You can also programatically switch to a Wildcard API Key by passing your current ip and a \* (asterisk) in the permitted\_ips field of the device-server POST call. E.g: \["1.2.3.4", "\*"].

Here is the full specification of the endpoint:

9. Sign the Session Request We are legally required to protect our users and their data from malicious attacks and intrusions. That is why we beyond having a secure https connection, we use asymmetric cryptography for signing requests that create a session or payment. The use of signatures ensures the data is coming from the trusted party and was not modified after sending and before receiving. In this step, you're preparing to create a session with bunq's API, which requires proving your identity using a digital signature. SESSION\_REQUEST\_BODY="{"secret":"$CREDENTIAL\_TOKEN"}" echo -n $SESSION\_REQUEST\_BODY > session.request

Make sure there is NO new line at the end of the file! Otherwise, the signature will be invalid.

Then you'll digitally sign the contents of the request body using your installation private key. This proves to bunq that the request really comes from someone in control of the private key tied to your public key. After, you'll encode the binary signature into base64 so it can be safely sent in the signature HTTP header from the next step. openssl dgst -sign installation.key -keyform PEM -sha256 -out signature < session.request SESSION\_REQUEST\_SIGNATURE=$(cat signature | base64)

Troubleshooting If you get an error telling you "The request signature is invalid", please check the following: There are no redundant characters (extra spaces, trailing line breaks, etc.) in the data to sign.

Make sure the body is appended to the data to sign exactly as you're adding it to the request.

You have added the full body to the data to sign.

You use the data to sign to create a SHA256 hash signature.

You have base64 encoded the SHA256 hash signature before adding it to the request under X-Bunq-Client-Signature.

You can find more info about signing the request body in this link here.

11. Create Session The POST /session-server endpoint is used to start a new session with the bunq API. Once your device is registered and you've created a valid credential, this call creates an authenticated session. The session ensures secure, time-limited access to the bunq API on behalf of your registered device and credentials. curl -X POST <https://public-api.sandbox.bunq.com/v1/session-server\\>
    -H "Accept: application/json"\
    -H "Content-Type: application/json"\
    -H "X-Bunq-Client-Request-Id: $(uuidgen)"\
    -H "X-Bunq-Client-Signature: $SESSION\_REQUEST\_SIGNATURE"\
    -H "X-Bunq-Client-Authentication: $TOKEN"\
    \--data "$SESSION\_REQUEST\_BODY"

The response will contain a session\_token. Use this token in the X-Bunq-Client-Authentication header for all subsequent API calls.

Here is the full specification of the endpoint:

✅ You're Ready! You've now successfully authenticated with the bunq Public API as a PSD2-certified provider. Now you're ready to set up the OAuth with your end user and start using the API in accordance with your certified roles (AISP, PISP, or CBPII). Please refer to this page on how to set up OAuth:

📝 Reminder All integration steps must be repeated in the production environment with your real eIDAS certificate when you're ready to go live.

Ready to continue? You can check what you can do with bunq's API according to your role in these pages here:


# Change your avatar

### How to change your avatar

#### 1. POST attachment-public

Start by creating an attachment that will be used for the avatar.

**Header**

Make sure you set the `Content-Type` header to match the MIME type of the image. It is also required you pass a description of the image via the `X-Bunq-Attachment-Description` header.

**Body**

The payload of this request is the binary representation of the image file. Do not use any JSON formatting.

**Response**

Save the `uuid` of the posted attachment. You'll need it to create the avatar in the next step.

{% openapi src="/files/xJ3v0GinyfONRbxz8Srh" path="/attachment-public" method="post" %}
[swagger.json](https://346554585-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FGE9Y1hc6C24r4Hen6KFH%2Fuploads%2Fgit-blob-218d6e618d396b5e315e30f903e92e143f7a4cc1%2Fswagger.json?alt=media)
{% endopenapi %}

#### 2. POST avatar

Make an avatar using the public attachment you've just created.

**Body**

The payload of this request is the `uuid` of the attachment public.

**Response**

In response, you’ll receive the UUID of the avatar created using the attachment. Save this UUID. You’ll use it as the avatar for the cash register you're about to create.

{% openapi src="/files/xJ3v0GinyfONRbxz8Srh" path="/avatar" method="post" %}
[swagger.json](https://346554585-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FGE9Y1hc6C24r4Hen6KFH%2Fuploads%2Fgit-blob-218d6e618d396b5e315e30f903e92e143f7a4cc1%2Fswagger.json?alt=media)
{% endopenapi %}


# Account Information Service Provider (AISP)

{% hint style="success" %}
Heya! Before checking this page, make sure that you [registered yourself as a TPP](/psd2/are-you-a-third-party-provider-tpp-start-here/register-as-a-tpp) in our API first.
{% endhint %}

As an AISP, you are allowed to authenticate in a user’s account and access (read) the following account information:

1. legal name
2. IBAN
3. nationality
4. card validity data
5. transaction history
6. account balance

To read the user's information, you need to establish a connection with their bunq account. You can do it using an **authorization request**. Once a bunq user has confirmed the authorization request and you have done the **token exchange**, you can activate the Access Token (use it as an API key).

Token activation happens when you create an API context (install it and link your IP adrress and device to it). See the [OAuth](/basics/authentication/oauth) page for the full flow illustration.

{% content-ref url="/pages/nQpuM79hqrXIE9iNgCIm" %}
[OAuth](/basics/authentication/oauth)
{% endcontent-ref %}

An active Access Token allows you to communicate with the bunq user’s account. You can use it to start a session to interact with the monetary accounts the user allows you to access.


# Payment Initiation Service Provider (PISP)

{% hint style="success" %}
Heya! Before checking this page, make sure that you [registered yourself as a TPP](/psd2/are-you-a-third-party-provider-tpp-start-here/register-as-a-tpp) in our API first.
{% endhint %}

As a PISP, you are allowed to authenticate in a user’s account with the following permissions:

1. read account information:
   * legal name;
   * IBAN;
2. initiate draft payments and read their statuses;
3. confirm that the account balance is sufficient for covering the payment.

The bunq API provides endpoints for different scenarios of the implementation of the payment initiation functionality. In particular, as a PISP user, you can build applications that initiate and authorize one-off or multiple incoming payments. Depending on the use case you are intending to deploy, you might need to initiate the OAuth authorization either before or after the payment initiation.

### Authorization of multiple (scheduled) payments <a href="#authorization-of-multiple-scheduled-payments" id="authorization-of-multiple-scheduled-payments"></a>

It is possible to initiate payments from a bunq user's account having previously established an OAuth connection between your application and the bunq user's account. The bunq user will receive push notifications for each initiated payment.

Once a bunq user has [confirmed they want to make payments via your application](https://beta.doc.bunq.com/psd2/connect-as-a-psd2-service-provider#register-your-application), you can initiate the payment confirmation flow.

1. Create a draft payment via `POST /user/{userID}/monetary-account/{monetary-accountID}/draft-payment`

   passing the following parameters:

   1. `monetary-accountId and userId` (`userApiKey`'s `id`; see [OAuth](/oauth) for more information) in the endpoint URL;
   2. the customer’s email address, phone number, or IBAN in the `counterparty_alias` field of the request body.
2. If the user confirms their intent to make the payment, bunq carries out the transaction.
3. Check the status of the payment via `GET /user/{userID}/monetary-account/{monetary-accountID}/draft-payment`

   using the draft payment `id` parameter returned in the previous step.

### Single payment authorization <a href="#single-payment-authorization" id="single-payment-authorization"></a>

It is possible to initiate payments having only the IBAN of the payer using `POST /user/{userID}/payment-service-provider-issuer-transaction`. In this case, the bunq user will accept the payment along with the authorization request. No additional push notifications are sent to the user.

1. Collect the bunq user's IBAN (and name) in the UI of your application.
2. Create a draft payment via `POST /user/{userID}/payment-service-provider-issuer-transaction`. More details here: [Payment Service Provider Issuer Transaction](/payment-service-provider/payment-service-provider-issuer-transaction)
3. Initiate an [authorization request](/basics/authentication/oauth). Upon the QR-code scan, the bunq user will see and be able to either accept or reject the payment authorization request.
4. Check the status of the payment.

### Get the end user information

You can get the user information by sending a calling the following endpoint with the `user_id`

{% hint style="success" %}
If you have an open session on behalf of the user, just call `GET /v1/user` (with no `user_id`) to retrieve their info.
{% endhint %}

{% openapi src="/files/xJ3v0GinyfONRbxz8Srh" path="/user/{itemId}" method="get" %}
[swagger.json](https://346554585-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FGE9Y1hc6C24r4Hen6KFH%2Fuploads%2Fgit-blob-218d6e618d396b5e315e30f903e92e143f7a4cc1%2Fswagger.json?alt=media)
{% endopenapi %}

### Confirm user account funds

You can check the availability of funds via `POST /user/{userID}/confirmation-of-funds` passing the following information:

* your `user_id`;
* the amount of money needed for the payment;
* the name of the bunq user and the IBAN of the account (email address or phone number pointing at the user are also possible).

Here the full specs of this endpoint:

{% openapi src="/files/xJ3v0GinyfONRbxz8Srh" path="/user/{userID}/confirmation-of-funds" method="post" %}
[swagger.json](https://346554585-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FGE9Y1hc6C24r4Hen6KFH%2Fuploads%2Fgit-blob-218d6e618d396b5e315e30f903e92e143f7a4cc1%2Fswagger.json?alt=media)
{% endopenapi %}


# Card-Based Payment Instrument Issuer (CBPII)

{% hint style="success" %}
Heya! Before checking this page, make sure that you [registered yourself as a TPP](/psd2/are-you-a-third-party-provider-tpp-start-here/register-as-a-tpp) in our API first.
{% endhint %}

As a CBPII, you are allowed to authenticate in a user’s account to validate the availability of funds for the payment in question.

1. Collect an alias for the bunq user's account (their name and IBAN, email address, or phone number).
2. Check the availability of funds via `POST /user/{userID}/confirmation-of-funds` passing the following information:
   * your `userId`;
   * the amount of money needed for the payment;
   * the name of the bunq user and the IBAN of the account (email address or phone number pointing at the user are also possible).

Here is the full specs of the endpoint:

{% openapi src="/files/xJ3v0GinyfONRbxz8Srh" path="/user/{userID}/confirmation-of-funds" method="post" %}
[swagger.json](https://346554585-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FGE9Y1hc6C24r4Hen6KFH%2Fuploads%2Fgit-blob-218d6e618d396b5e315e30f903e92e143f7a4cc1%2Fswagger.json?alt=media)
{% endopenapi %}


# Troubleshooting as a Third Party Provider (TPP)


# API Support Team

{% hint style="success" %}
**This support channel is only for API-related questions.**\
This means we can assist you only if you're building on top of the bunq API, have questions about OAuth, or are working with PSD2 integrations.

For anything else, please contact our [bunq Customer Support.](https://together.bunq.com/d/32181-how-do-i-contact-support)
{% endhint %}

If you have questions, encounter bugs, or need help integrating with our API, you can reach us directly via email: [**apipartner@bunq.com**](mailto:apipartner@bunq.com)\
To help us assist you faster, please include the following in your message:

* ✅ **Environment**: `production` or `sandbox`
* 🕒 **Timestamp** of the issue (preferably in UTC — you can find this in our response headers)
* 🔁 **Response ID** (from the API response headers)
* 👥 **How many users are affected** (especially relevant for PSD2 partners that grant access to third-party users)
* 🔍 **Endpoint or feature** involved
* 📝 A short description of the problem, including any **error messages or codes**


# FAQ

{% hint style="success" %}
Don't know what to do? You can always get in touch with us via support in your bunq app!
{% endhint %}

Welcome to our FAQ page! Here you'll find answers to the most common questions about integrating with the bunq API, including setup requirements, authentication, and support. Whether you're just getting started or looking for specific details, this page is here to help you build with confidence.

We are constantly updating this page, so keep a look out to the most common questions!

<details>

<summary>If you are a bunq user</summary>

#### What do I need to integrate bunq's API?

To integrate with bunq's API, you'll need:

* An **API key**, which you can generate from the bunq app.
* A **public/private key pair** to securely sign requests.
* To complete the **installation, device, and session setup** process to authenticate your API calls.

The API uses standard REST principles and communicates via JSON. You can find setup guides and examples here:

\
[bunq API documentation](https://doc.bunq.com/)

#### What is PSD2?

PSD2 (Payment Services Directive 2) is an EU regulation designed to make electronic payments more secure and to encourage innovation in financial services. It allows **licensed third-party providers (TPPs)**—such as fintech apps or financial aggregators—to access bank account information and initiate payments on behalf of users, with their explicit consent.

In the context of the **bunq API**, PSD2 is especially relevant if you plan to:

* Access **user account information** (AIS — Account Information Services)
* Initiate **payments on a user’s behalf** (PIS — Payment Initiation Services)

If you are a licensed TPP operating under PSD2, you can integrate with bunq using a **PSD2 certificate**. This grants you direct access to user accounts in a regulated and compliant way.

Alternatively, if you're **not a licensed TPP**, but still want users to connect their bunq accounts to your app (for example, to show their balance or history), keep in mind that you might need a certification for those operations.

</details>

<details>

<summary>If you are a PSD2 provider</summary>

#### **What certificate types does bunq accept?**

We accept QSEAL certificates.

#### **How do I get an API key as a PSD2 user?**

You’ll receive a token in return to passing your QSEAL certificate. You’ll then be able to use it as an API key.

#### **What do I need to register my QSEAL certificate?**

You need to send a POST /payment-service-provider-credential request passing your QSEAL certificate, QSEAL certificate chain, and a signature of the device registration key with the QSEAL private key. Check the full authentication flow here.

#### **How does the PSD2 API request signing work?**

The request signature requirement is the same for all types of API users. You can read more about it here.

#### **How do I get an OAuth client as a PSD2 user?**

You can create an OAuth Client using `POST /user/userId/oauth-client` and read it using its id (`GET /user/userId/oauth-client/oauth-clientId`). Once you have it, you can add a redirect url using `POST /user/{userID}/oauth-client/{oauth-clientID}/callback-url`. From there, you can move to creating an authorization request and token exchange.

Check more about it here:

[OAuth](/basics/authentication/oauth)

#### **Where do I find the AISP, PISP and CBPIII API reference?**

bunq offers one API to both our users and companies with PSD2 permit: <https://doc.bunq.com/>. As a PSD2 party, you can only access the endpoints that correspond with your permit level.

#### **Are the AISP, PISP and CBPIII APIs the same API as I use as a bunq user?**

Yes, the only thing that differs is the authentication flow. Everything else is the same.

#### **I need help with the PSD2 API. How do I get support?**

[Our API documentation](/psd2/are-you-a-third-party-provider-tpp-start-here) will help you get started. If you need dedicated guidance or troubleshooting for a problem you are experiencing, you can purchase a [10-hour dedicated support package](https://www.bunq.com/assets/media/legal/en/20200805_consumers_business_pricing_EN.pdf). You can request this service at <apipartner@bunq.com>.

</details>


# Your first payment

This tutorial will walk you through every step to make your first payment through the bunq API. It helps you set up a installation and session but also explains how to get a sandbox user, get sandbox money on that account.\
\
For everyone unfamiliar with the bunq API we recommend to finish the tutorial in your favorite programming language to get a intuition for the bunq API. It's very likely that you'll reuse some code later in your own projects.


# Introduction

### What You'll Learn in this tutorial

In this tutorial, we'll walk through the process of making a payment using the bunq API. Including obtaining the API key, getting sandbox money and making your first call.

* **Phase: Orientation**
  * Determine if you have the skills and tools needed for the task.
  * Explore resources like Tinker, SDKs, and Postman.
* **Phase: Obtain Your API Key**
  * Secure your API key to start interacting with the bunq API.
* **Phase: Setting Up Your Sandbox User**
  * Learn to set up a sandbox user and understand your user object.
  * Obtain sandbox money for testing transactions.
  * Refresh your API session efficiently from your script.
  * Set up a device with the bunq app to verify your operations.
* **Phase: List Your Stuff**
  * Make GET requests to list monetary accounts, cards, and transactions.
* **Phase: Payments**

  * Execute POST requests to make payments, drafts, and requests.
  * Transition to production and explore more advanced endpoints.

  By following these steps, you will gain practical experience with the bunq API and be prepared to handle real-world banking operations through your application.

### What You'll Need

Before you begin, ensure you have the following:

* A bunq account to access the API.
* Basic programming knowledge and understanding of HTTP requests.
* Familiarity with tools like Postman for testing API calls.

### What is next

Up next we'll generate a sandbox user and obtaining a API key


# Creating a sandbox user and getting an API key

## Creating a sandbox user

In order to use the bunq API you'll need a bunq user. While you are building and testing you don't want to use your own bunq account, as this contains your money, and mistakes have real consequences. To give you maximum flexibility we give you the option to generate a sandbox user.

A sandbox user behaves in very much the same way as your own bunq account. So when you generate one, your user will have a name place of birth, phone number and all other information that belongs to a bank account. From there you can use this user to build and test your app. If something ever goes wrong, you can simply generate a new user.

You can log into an app of your sandbox user [Sandbox version of the bunq app](/tutorials/your-first-payment/setting-up-a-sandbox-user/sandbox-version-of-the-bunq-app) and also get (fake) money on the sandbox bank account that belongs to your user [Getting sandbox money on the user account](/tutorials/your-first-payment/setting-up-a-sandbox-user/getting-sandbox-money-on-the-user-account). When you are ready to start using your app with real money you can start [Moving to production](/basics/moving-to-production).

It's good to know there are 2 types of users you can generate. One is user-person and the other is user-company. The difference is that a user person represents a bank account for an individual. User Company creates a business bank account.

## Sandbox API Keys

An API key is a unique identifier that grants access to our services and is linked to a specific user account. **Think of it like a key to your bank account**—keep it secure to prevent unauthorized access to your data and transactions.

For development and testing, we offer **sandbox users**, which simulate real accounts, including balances, cards, and payment capabilities. This allows you to test and integrate your system safely without impacting live data. You can find more details on how to create and use a sandbox user here.

{% hint style="danger" %}
**DO NOT** commit your API key into your source control provider (Github/Gitlab/etc).

If you end up doing so you can always revoke the key from your bunq app, or generate a new user here.
{% endhint %}

### Create a sandbox user

To create a user and obtain an API key you can just run 1 command from your terminal:

Run this command to obtain a user person. It will return just a API key, but in the background we generated a dummy user for you to play around with in the sandbox.

```bash
curl --location --request POST 'https://public-api.sandbox.bunq.com/v1/sandbox-user-person'
```

Run this command to obtain a business user

```bash
curl --location --request POST 'https://public-api.sandbox.bunq.com/v1/sandbox-user-company'
```

This request will create a new user in the system. The response will give you the API key that belongs to this user. Store it somewhere safe.

```json

{
    "Response": [
        {
            "ApiKey": {
                "api_key": "sandbox_a918ac413524f2bf56ceb740595e01839dd7f0321ca08e4c4ea93349"
            }
        }
    ]
}
```

## Code samples

#### Creating a user-person:

{% openapi src="<https://raw.githubusercontent.com/bunq/doc/master/swagger.json>" path="/sandbox-user-person" method="post" %}
<https://raw.githubusercontent.com/bunq/doc/master/swagger.json>
{% endopenapi %}

#### Creating a user-company:

{% openapi src="<https://raw.githubusercontent.com/bunq/doc/master/swagger.json>" path="/sandbox-user-company" method="post" %}
<https://raw.githubusercontent.com/bunq/doc/master/swagger.json>
{% endopenapi %}

### What's next

We can almost start making calls with this API key we now have. However because of PSD2 and security reasons we first will need to register a Installation and device. Once we have those we will get a session and our user details.


# Creating the API Context

When integrating with the bunq API, security is a top priority. Instead of relying solely on API keys, bunq uses a **three-step authentication process:**

* installation,
* device registration,
* session creation

to ensure secure and controlled access. This approach prevents unauthorized use, protects sensitive financial data, and aligns with banking security standards like PSD2. Here’s how each step enhances security while keeping API access flexible and manageable.

{% hint style="success" %} <mark style="background-color:blue;">**Pro Tip: Simplify with Postman**</mark>

Using our Postman examples makes API integration super easy. We've pre-filled all the requests with environment variables, streamlining the process and reducing the risk of errors. It's by far the quickest way to get a live session, to play around with until you fully commit to a integration in your own application.

This setup allows you to focus more on development and less on configuration, ensuring a smoother experience.

<https://github.com/bunq/postman/>
{% endhint %}

### How it all hangs together

The installation call is the only call you can make with without any authorization token.

<table data-full-width="true"><thead><tr><th>Call</th><th>Needs in the header</th><th>Needs in the body</th><th>Returns</th></tr></thead><tbody><tr><td><strong>/installation</strong></td><td>Nothing</td><td>The public key (generated by you) of your server</td><td>1. installation token<br>2. public key of the server</td></tr><tr><td><strong>/device-server</strong></td><td>The installation token</td><td>the API key of your user and you server IP that will have access to bunq's API</td><td>A device ID</td></tr><tr><td><strong>/session-server</strong></td><td>Installation token</td><td>the API key of your user</td><td>Session token and user object</td></tr></tbody></table>

### What's next

In the following pages you'll have a closer look on how to create your API Context by calling each of the 3 endpoints we explained here.


# Creating the Installation

### 1. Installation

Installation is the first step in activating your API key!

It creates an **API context**, which is a secure foundation for all future interactions. This step ensures that the API key is properly registered and associated with a trusted user, reducing the risk of unauthorized access. Without installation, the API key alone could be misused if leaked. This is similar to setting up encryption keys before securely exchanging data.

Firstly, you'll need to generate a public-private key pair using OpenSSL following these steps:

1. Open a terminal.
2. Run the following command to generate the key pair:

   ```powershell
   openssl genrsa -out installation.key && openssl rsa -in installation.key -outform PEM -pubout -out installation.pub
   ```
3. Your installation key pair will be stored as:

   ```powershell
   $(pwd)/installation.pub #your public-key
   $(pwd)/installation.key #your private-key
   ```
4. Copy the installation.pub value

   ```powershell
   cat ~(pwd)/installation.pub | pbcopy #this command can be different depending on your Operating System.
   ```

After generating your key pair, you'll be able to pass the `installation.pub` in the request body as value for the field `client_public_key`.

Here you can find all required fields for the installation endpoint:

{% openapi src="/files/xJ3v0GinyfONRbxz8Srh" path="/installation" method="post" %}
[swagger.json](https://346554585-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FGE9Y1hc6C24r4Hen6KFH%2Fuploads%2Fgit-blob-218d6e618d396b5e315e30f903e92e143f7a4cc1%2Fswagger.json?alt=media)
{% endopenapi %}

### Retrieving Server Public Key

If at any point you want to retrieve the public key of the server you simply make a GET rquest on the installation.

## GET /installation/{installationID}/server-public-key

> Show the ServerPublicKey for this Installation.

```json
{"openapi":"3.0.0","info":{"title":"bunq API","version":"1.0"},"tags":[{"name":"server-public-key","description":""}],"servers":[{"url":"https://public-api.sandbox.bunq.com/{basePath}","description":"Sandbox server","variables":{"basePath":{"default":"v1"}}},{"url":"https://api.bunq.com/{basePath}","description":"Production server","variables":{"basePath":{"default":"v1"}}}],"paths":{"/installation/{installationID}/server-public-key":{"get":{"tags":["server-public-key"],"summary":"","operationId":"List_all_ServerPublicKey_for_Installation","description":"Show the ServerPublicKey for this Installation.","parameters":[{"in":"path","name":"installationID","description":"","required":true,"schema":{"type":"integer"}},{"$ref":"#/components/parameters/Cache-Control"},{"$ref":"#/components/parameters/User-Agent"},{"$ref":"#/components/parameters/X-Bunq-Language"},{"$ref":"#/components/parameters/X-Bunq-Region"},{"$ref":"#/components/parameters/X-Bunq-Client-Request-Id"},{"$ref":"#/components/parameters/X-Bunq-Geolocation"},{"$ref":"#/components/parameters/X-Bunq-Client-Authentication"}],"responses":{"200":{"description":"Using /installation/_/server-public-key you can request the ServerPublicKey again. This is done by referring to the id of the Installation.","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/InstallationServerPublicKeyListing"}}}},"headers":{"X-Bunq-Client-Response-Id":{"$ref":"#/components/headers/X-Bunq-Client-Response-Id"},"X-Bunq-Client-Request-Id":{"$ref":"#/components/headers/X-Bunq-Client-Request-Id"},"X-Bunq-Server-Signature":{"$ref":"#/components/headers/X-Bunq-Server-Signature"}}},"400":{"$ref":"#/components/responses/GenericError"}}}}},"components":{"parameters":{"Cache-Control":{"description":"The standard HTTP Cache-Control header is required for all signed requests.","schema":{"type":"string"},"required":false,"in":"header","name":"Cache-Control"},"User-Agent":{"description":"The User-Agent header field should contain information about the user agent originating the request. There are no restrictions on the value of this header.","schema":{"type":"string"},"required":true,"in":"header","name":"User-Agent"},"X-Bunq-Language":{"description":"The X-Bunq-Language header must contain a preferred language indication. The value of this header is formatted as a ISO 639-1 language code plus a ISO 3166-1 alpha-2 country code, separated by an underscore. Currently only the languages en_US and nl_NL are supported. Anything else will default to en_US.","schema":{"type":"string"},"required":false,"in":"header","name":"X-Bunq-Language"},"X-Bunq-Region":{"description":"The X-Bunq-Region header must contain the region (country) of the client device. The value of this header is formatted as a ISO 639-1 language code plus a ISO 3166-1 alpha-2 country code, separated by an underscore.","schema":{"type":"string"},"required":false,"in":"header","name":"X-Bunq-Region"},"X-Bunq-Client-Request-Id":{"description":"This header must specify an ID with each request that is unique for the logged in user. There are no restrictions for the format of this ID. However, the server will respond with an error when the same ID is used again on the same DeviceServer.","schema":{"type":"string"},"required":false,"in":"header","name":"X-Bunq-Client-Request-Id"},"X-Bunq-Geolocation":{"description":"This header must specify the geolocation of the device. The format of this value is longitude latitude altitude radius country. The country is expected to be formatted of an ISO 3166-1 alpha-2 country code. When no geolocation is available or known the header must still be included but can be zero valued.","schema":{"type":"string"},"required":false,"in":"header","name":"X-Bunq-Geolocation"},"X-Bunq-Client-Authentication":{"description":"The authentication token is used to authenticate the source of the API call. It is required by all API calls except for POST /v1/installation. It is important to note that the device and session calls are using the token from the response of the installation call, while all the other calls use the token from the response of the session-server call","schema":{"type":"string"},"required":true,"in":"header","name":"X-Bunq-Client-Authentication"}},"schemas":{"InstallationServerPublicKeyListing":{"type":"object","properties":{"server_public_key":{"type":"string","description":"The server's public key for this Installation.","readOnly":true,"writeOnly":false}}},"Error":{"type":"array","items":{"type":"object","properties":{"error_description":{"type":"string","description":"The error description in English."},"error_description_translated":{"type":"string","description":"The error description translated to the user's language."}}}}},"headers":{"X-Bunq-Client-Response-Id":{"description":"A unique ID for the response formatted as a UUID. Clients can use it to add extra protection against replay attacks.","schema":{"type":"string"}},"X-Bunq-Client-Request-Id":{"description":"This header must specify an ID with each request that is unique for the logged in user. There are no restrictions for the format of this ID. However, the server will respond with an error when the same ID is used again on the same DeviceServer.","schema":{"type":"string"},"required":false},"X-Bunq-Server-Signature":{"description":"The server's signature for this response. See the signing page for details on how to verify this signature.","schema":{"type":"string"}}},"responses":{"GenericError":{"description":"This is how the error response looks like for 4XX response codes","headers":{"X-Bunq-Client-Response-Id":{"$ref":"#/components/headers/X-Bunq-Client-Response-Id"},"X-Bunq-Client-Request-Id":{"$ref":"#/components/headers/X-Bunq-Client-Request-Id"},"X-Bunq-Server-Signature":{"$ref":"#/components/headers/X-Bunq-Server-Signature"}},"content":{"application/json":{"schema":{"type":"object","properties":{"Error":{"$ref":"#/components/schemas/Error"}}}}}}}}}
```

###

### What's next

With the `installation_token` and the `server_public_key` in hands, we are able to authenticate ourselves and register our device for future endpoint calls.


# Device Registration

Registering a device links the API key to a specific phone, computer, or in the case of the API a server.

By using `POST /device-server`, bunq ensures that only pre-approved machines can make API calls. This approach is crucial in banking, where unauthorized access could lead to financial fraud. Unlike simple API keys, this extra layer significantly **reduces attack surfaces** by restricting access to known, registered devices.

{% hint style="warning" %}
Ensure you have a **bunq API Key**. You can generate one from the bunq APP if you haven't already.
{% endhint %}

#### IP addresses

When you use a standard API Key, the `DeviceServer` and `Installation` are linked to the IP address where they were created. You won’t be able to add new IP addresses later.

Using a Wildcard API Key gives you the freedom to make API calls from any IP address after the `POST device-server`. You can switch to a Wildcard API Key by tapping on “Allow All IP Addresses” in your API Key menu inside the bunq app. You can also programatically switch to a Wildcard API Key by passing your current ip and a `*` (asterisk) in the `permitted_ips` field of the device-server POST call.

Payload example:

```json
{
	"description": "My awesome APP",
	"secret": "{api_key}",
	"permitted_ips": ["1.2.3.4", "*"]
}
```

{% hint style="info" %}
bunq currently only accepts IPV4 addresses. IPV6 is not supported right now.
{% endhint %}

#### Code samples for the device-server endpoint:

{% hint style="warning" %}
Pass the `installation_token` received in the last step as value for the header `X-Bunq-Client-Authentication` to authenticate yourself in this endpoint.
{% endhint %}

{% openapi src="/files/xJ3v0GinyfONRbxz8Srh" path="/device-server" method="post" %}
[swagger.json](https://346554585-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FGE9Y1hc6C24r4Hen6KFH%2Fuploads%2Fgit-blob-218d6e618d396b5e315e30f903e92e143f7a4cc1%2Fswagger.json?alt=media)
{% endopenapi %}

### What's next

With the `installation_token` in hands, we are able to authenticate ourselves and register our device for future endpoint calls.


# Start a Session

Sessions provide **temporary authentication**, meaning that even if a session token is exposed, it will automatically expire after a set period. This balances security with usability—developers don’t need to constantly re-authenticate, but access remains limited to prevent long-term misuse. The session timeout can be adjusted in the bunq app, giving users control over their security settings. This session-based approach is common in **high-security environments**, ensuring that access is time-limited and reducing persistent threats.

Once you have a session we'll return you the user object. So you have all details of your user.

The response of the session API call is a object with a **session token**, this is the token we'll use in subsequent requests to authenticate requests. There is also a **userid** in this response. Which we will use in the next calls.

```json

{
    "Response": [
        {
            "Id": {
                "id": 25536694
            }
        },
        {
            "Token": {
                "id": 25536694,
                "created": "2025-03-05 16:12:11.918100",
                "updated": "2025-03-05 16:12:11.918100",
                "token": "ce717e3001d979ff5e22bac13508b46e7ad740971d1d75c5371614e546ca8b83"
            }
        },
        {
            "UserPerson": { "id": 1822179,
                            "rest of the user object":...  }
        }
    ]
}

```

{% hint style="info" %}
**If your session expires you simply call the /session-server endpoint again. This gives you a new session token to use**
{% endhint %}

{% openapi src="/files/xJ3v0GinyfONRbxz8Srh" path="/session-server" method="post" %}
[swagger.json](https://346554585-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FGE9Y1hc6C24r4Hen6KFH%2Fuploads%2Fgit-blob-218d6e618d396b5e315e30f903e92e143f7a4cc1%2Fswagger.json?alt=media)
{% endopenapi %}

### What's next:

You'll see that with the session we obtained the details of your user. If you look closely you can see that the [User](/basics/bunq-api-objects/user) object is also returned with this call. If you're still using Postman it's a good idea to already note the user ID as that is used in many of the subsequent API calls.

Up next we'll set up this user object so that you can use it in your sandbox.


# Setting up a sandbox user

Let's update your user so that we can use it.


# Retrieving my user details

In the previous chapter you already requested a session and as a response received the user object. If you saved that in your app you already have a reference to who your user is. The most important item to keep track of is the userid. As that's what we'll need in the next few steps.

#### What we have done so far

* We have created a sandbox user, and generated an API key
* We have created a installation and registered a device
* We generated a session and now know the user ID and Session token

In the next chapter we'll use these to make our API calls

## Who is my user, and what is his current balance?

To retrieve our user we simply can do a API call to the user endpoint and this will return us our current user. We already had this information from the previous step. The important thing to save here is the user ID. We'll need to pass it as a url parameter in the next calls.

{% openapi src="<https://raw.githubusercontent.com/bunq/doc/master/swagger.json>" path="/user" method="get" %}
<https://raw.githubusercontent.com/bunq/doc/master/swagger.json>
{% endopenapi %}

## So, what is the balance of our user?

We'll we need just one more step that is to call the monetary-account-bank endpoint. This endpoint lists all monetary accounts we know for this user. The response contains a list of monetary accounts.

Take note of the monetary account ID, most of the API calls reference both the user ID as well as the ID of the thing you are performing your actions on. To answer the question: what is the balance of our user. You can see that the newly created user comes with 1 monetary account, with a balance of €0.00. Let's add some money to the account!

```json
"Response": [
        {
            "MonetaryAccountBank": {
                "id": 2026814,
                "created": "2025-03-05 16:12:01.255073",
                "updated": "2025-03-05 16:12:01.255073",
                ... more key value pairs ... 
                }
                "balance": {
                        "currency": "EUR",
                        "value": "0.00"
                    },

        }
    ]
```

{% openapi src="<https://raw.githubusercontent.com/bunq/doc/master/swagger.json>" path="/user/{userID}/monetary-account-bank" method="get" %}
<https://raw.githubusercontent.com/bunq/doc/master/swagger.json>
{% endopenapi %}


# Getting sandbox money on the user account

The easiest way to become a millionaire is in our sandbox. This is because we have 1 user in our sandbox called Sugar Daddy. This user will approve all requests for money up to €500 at a time.

## Call to SugarDaddy

As you can see in the response we'll need to pass in our user ID as well as monetary account ID. counterparty Alias is the alias (phone number, email address or IBAN) of the person you want money from. The amount and currency are €100.

{% hint style="info" %}
Sugardaddy hands out free money, it's not unlimited. Sugardaddy will only accept requests up to €500,- Any request above that amount will be rejected.
{% endhint %}

#### Example request body

```json
{
    "amount_inquired": {
        "value": "100",
    "currency": "EUR"
  },
    "counterparty_alias": {
      "type": "EMAIL",
      "value": "sugardaddy@bunq.com",
      "name": "Sugar Daddy"
  },
  "description": "You'\''re the best!",
  "allow_bunqme": false
}'
```

#### Example curl call to sugardaddy

```
curl --location 'https://public-api.sandbox.bunq.com/v1/user/1822179/monetary-account/2026814/request-inquiry' \
--header 'Content-Type: application/json' \
--header 'Cache-Control: no-cache' \
--header 'User-Agent: postman' \
--header 'X-Bunq-Language: en_US' \
--header 'X-Bunq-Region: nl_NL' \
--header 'X-Bunq-Client-Request-Id: c27ctel8WBQrleTTk4I8' \
--header 'X-Bunq-Geolocation: 0 0 0 0 000' \
--header 'X-Bunq-Client-Authentication: ce717b3001d979ff5e22bac13508b46e7ad740971d1d75c5371614e546ca8b83' \
--header 'X-Bunq-Client-Signature: IQmnUmd/DO8ELKBGKSuSdszxQmvBNA1s7ONHdmYU9glRs1kpaDnVmPyQgFL+mDffbdm5G4KsoI4Wvt07Cw1upYN8aPb0MEzpKL54F/1f2AZxWxUTGT+uBvdyCZuT609NJc279casOL1zm0XNo2i2Q8NiFKl0Y4eCmEOyErvxjAWuLObOW7VzXcWFN1LqXLC12sDGKTagbXtRrSS/mL5fsKYCxWQrb9mBsAWzmFWC0mGORrzrpUG24r+1qX/sRsH6wMKf5t8nUIsBeGtT0c724wM9M/Silp0OoZQ0w37nuiKGxPq+Awxx4//o2b09wIKyZUBEeFa1BQnrLdAIKvABLA==' \
--data-raw '{
	"amount_inquired": {
    "value": "100",
    "currency": "EUR"
  },
  "counterparty_alias": {
      "type": "EMAIL",
      "value": "sugardaddy@bunq.com",
      "name": "Sugar Daddy"
  },
  "description": "You'\''re the best!",
  "allow_bunqme": false
}'
```

#### Example response body

Sugardaddy just returns a response with a request ID. But since we don't see an error we know that Sugardaddy received the request

```json
{
    "Response": [
        {
            "Id": {
                "id": 1475092
            }
        }
    ]
}
```

#### Verification

If we want to double check that Sugar Daddy indeed paid us we'll have to double check our Monetary account again. Instead of listing all monetary accounts we can now also do a smaller API call to just get details for the monetary account in our request. Which should have our updated balance in the response

#### request

```http
curl --location 'https://public-api.sandbox.bunq.com/v1/user/1822179/monetary-account/2026814' 
\
--header 'Content-Type: application/json' \
--header 'Cache-Control: no-cache' \
--header 'User-Agent: postman' \
--header 'X-Bunq-Language: en_US' \
--header 'X-Bunq-Region: nl_NL' \
--header 'X-Bunq-Client-Request-Id: c27ctel8WBQrleTTk4I8' \
--header 'X-Bunq-Geolocation: 0 0 0 0 000' \
--header 'X-Bunq-Client-Authentication: ce718b3001d979ff5e22bac13508b46e7ad740971d1d75c5371614e546ca8b83' \
--header 'X-Bunq-Client-Signature: IQmnUmd/DO8EEKBGKSuSdszxQmvBNA1s7ONHdmYU9glRs1kpaDnVmPyQgFL+mDffbdm5G4KsoI4Wvt07Cw1upYN8aPb0MEzpKL54F/1f2AZxWxUTGT+uBvdyCZuT609NJc279casOL1zm0XNo2i2Q8NiFKl0Y4eCmEOyErvxjAWuLObOW7VzXcWFN1LqXLC12sDGKTagbXtRrSS/mL5fsKYCxWQrb9mBsAWzmFWC0mGORrzrpUG24r+1qX/sRsH6wMKf5t8nUIsBeGtT0c724wM9M/Silp0OoZQ0w37nuiKGxPq+Awxx4//o2b09wIKyZUBEeFa1BQnrLdAIKvABLA==' \
--data ''
```

#### Response

```
"Response": [
        {
            "MonetaryAccountBank": {
                "id": 2026814,
                "created": "2025-03-05 16:12:01.255073",
                "updated": "2025-03-05 16:12:01.255073",
                ... more key value pairs ... 
                }
                "balance": {
                        "currency": "EUR",
                        "value": "100.00"
                    },

        }
    ]
```

## Requesting money from other users

If you want to request money from other users then this is how you do it: Just be aware, sugardaddy is the only one who automatically transfers money to you.

{% openapi src="<https://raw.githubusercontent.com/bunq/doc/master/swagger.json>" path="/user/{userID}/monetary-account/{monetary-accountID}/request-inquiry" method="get" %}
<https://raw.githubusercontent.com/bunq/doc/master/swagger.json>
{% endopenapi %}

## Response

```json
{
    "Response": [
        {
            "Id": {
                "id": 1475092
            }
        }
    ]
}
```


# Sandbox version of the bunq app

Although Postman offers a great way to interact with our systems you might want to verify how something looks for a end-user in the app. Or you may want to have a device with a 2nd sandbox user to easily make payments with.

## Downloads:

* You can download the Sandbox app APK file for Android [here](https://appstore.bunq.com/api/android/builds/bunq-android-sandbox-master.apk)
* You can download android Studio [here](https://developer.android.com/studio)

### Setting up a fresh user

* You'll need to create a brand new user person to use the sandbox app.
* The reason for this is that our sandbox environment will only let you pass our security measures on the 1st API call that that user makes.
* As such you'll call the [Sandbox Users](/sandbox-users) endpoint.
* From that response note down the aliases (either phone number or email address) write that down for later.
* In the response you'll also find a login code. Usually 000000
* Be sure to **not** make any other api calls from this user for now.

## Installing the sandbox app on your physical device

* **Download the APK**
  * Ensure it’s saved in your device’s **Downloads** folder or any other location.
* **Enable Unknown Sources** (if not enabled)
  * Open **Settings** → **Apps & notifications** (or **Apps**).
  * Tap **Special app access** → **Install unknown apps**.
  * Select the app (e.g., Chrome, File Manager) that you’re using to open the APK.
  * Toggle **"Allow from this source"** ON.
* **Install the APK**
  * Open **File Manager** and navigate to the APK file.
  * Tap on the file and press **Install**.
  * Wait for the installation to complete.
* **Open the App**
  * Once installed, tap **Open** to launch the app.

## Starting the Android Device Manager

1. Once you have opened Android studio tap the dropdown menu and choose

<figure><img src="/files/BUs98ZNpYus23KDuqHFt" alt=""><figcaption></figcaption></figure>

2. From that screen you can easily create a new device<br>
   1. Start the wizard by clicking on "+ Create Virtual Device".
   2. Select a device (recommendation: "Pixel 5.0" or "Nexus 6") and press "Next".
   3. Select an x86 system image (recommendation: Nougat, API Level 25, Android 7.1.1 with Google APIs) and press "Next". The image needs to have Google Play Services 10.0.1 or higher.
   4. In the bottom left corner, select "Show Advanced Settings".
   5. Scroll to "Memory and Storage".
   6. Change "Internal Storage" to "2048 MB".
   7. Change "SD card" to "200 MB".
   8. Press "Finish".

<figure><img src="/files/FnFcjByKw5oI5SBMUbd9" alt=""><figcaption></figcaption></figure>

3. You can now start the virtual device by selecting the play button.
4. Installing thebunq Sandbox App APK
5. From the Virtual device simply navigate to our download link [here](https://appstore.bunq.com/api/android/builds/bunq-android-sandbox-master.apk) and download and install the app. You should now have the bunq app on your desktop

<figure><img src="/files/7BQaDR8kGcF5klyenyGM" alt=""><figcaption></figcaption></figure>

## Logging into a your user

In the fresh user you just created you saved the phone number or email addres. Now use those to **log in** to the app. The passcode is usually 000000 or 992266.

If you (for whatever reason) log out and back in again you should be able to since it's the same device. But you won't be able to repeat this on a new device as only the first login on sandbox bypasses our security.

{% hint style="info" %}
Be aware that you cannot create a user from the signup flow in the app. As we're not reviewing the users created in sandbox manually
{% endhint %}


# First Payments

In the previous chapter you already made a request to Sugar Daddy to get money on your sandbox account and you made API calls to retrieve the balance on your accounts. If you have that implemented in your application you're already well on your way to manipulate the majority of objects in the bunq API. We highly recommend you take a look at [API Reference](/api-reference/hidden-references) to see all the endpoints we offer.

But before we let you go we have to get to the good stuff: making a payment. The reason we make it a separate topic is that we got quite some feedback that people get stuck on something called 'signing'.

### What is signing and why does it matter

In short: if you use our API to make a payment you don't want a man-in-the-middle to be able to meddle with your request. If you made a payment for let's say €10, then you don't want anyone to hijack that request and make the payment €12. Signing prevents that from happening as we create a signature that allows the bunq backend to validate that the content of the request has not been tampered with. You can read much more about signing here [Signing](/basics/signing). In this tutorial we'll just show you how.

## Making a Payment

The API call for making a payment is nothing special (except for the signing) so you can just make a call using the method described below.

Some points of attention:

* The counterparty is a pointer towards a user [bunq API Objects](/basics/bunq-api-objects)
* You can construct your own using json

<pre class="language-json"><code class="lang-json">      {
            "amount": {
                "value": 10,
                "currency": "EUR"
            },
            "counterparty_alias": {
                "type": "EMAIL",
                "value": "sugardaddy@bunq.com",
                "name": "Sugar Daddy"
            },
            "description": "here is your money back"
    }
<strong>    
</strong></code></pre>

## Signing

Now one of the things we need to do before we can actually make the API call is to generate a signature and add it to our \`'X-Bunq-Client-Signature' in the header of the API call.

The specifics on how to do that are in [Signing](/basics/signing) on that page you'll also find some coding examples. For instance in python [Full signing.py](/basics/signing/python-code-example/full-signing.py)

From there it's nothing different than a normal API call and you just post to the endpoint and will receive a ID of the generated payment.

{% openapi src="/files/xJ3v0GinyfONRbxz8Srh" path="/user/{userID}/monetary-account/{monetary-accountID}/payment" method="post" %}
[swagger.json](https://346554585-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FGE9Y1hc6C24r4Hen6KFH%2Fuploads%2Fgit-blob-218d6e618d396b5e315e30f903e92e143f7a4cc1%2Fswagger.json?alt=media)
{% endopenapi %}


# How to do bulk payments

XML limit is 350 payments per batch


# Receiving payments on your website using bunq.me

Imagine you're running a charity and want to collect donations directly through your website. After someone donates, you'd like to redirect them to a custom "Thank You" page— to show appreciation, share what their donation supports, or even offer a downloadable gift or receipt.

In this tutorial, you’ll learn how to use the bunq API to:

* Create a payment link you can share or embed on your site
* Add a redirect URL that sends users to a confirmation or thank-you page after payment
* Make the experience smooth, branded, and personal

This works great for charities, event registrations, fundraising campaigns, or any situation where you want to acknowledge someone after they’ve completed a payment.

Let’s dive in and build a seamless donation flow with a touch of gratitude 💚

***

## Let's create a link that we can embed in our own website

In order to create a custom link we'll need the bunq.me-tab endpoints. This allows you to create a payment request and embed the link on your site.

## We start by creating a bunq.me tab

We post the following body. Note that you can set a redirect URL to your own thank you page. You can use this can also be used to add url parameters, for example a user id so you can retrieve the user in your own database. For this example we'll just redirect to the bunq homepage

```json

{
  "bunqme_tab_entry": {
    "amount_inquired": {
      "value": "10.00",
      "currency": "EUR"
    },
    "description": "Donation",
    "redirect_url": "https://bunq.com"
  },
  "status": "string",
  "event_id": 0
}

```

This returns us a simple response with the ID of our new bunq.me tab

```json

{
    "Response": [
        {
            "Id": {
                "id": 7567
            }
        }
    ]
}
```

## API call

{% openapi src="/files/xJ3v0GinyfONRbxz8Srh" path="/user/{userID}/monetary-account/{monetary-accountID}/bunqme-tab" method="post" %}
[swagger.json](https://346554585-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FGE9Y1hc6C24r4Hen6KFH%2Fuploads%2Fgit-blob-218d6e618d396b5e315e30f903e92e143f7a4cc1%2Fswagger.json?alt=media)
{% endopenapi %}

## So we created the tab, but what is the URL we can send our user to?

We'll need to make a second API call in order to retrieve all data. This one is simply a get request using the ID that we got from our POST message (see below. It returns us a object with one key called `bunq-me-tab-share-url`

This url is what you can add to your website. For instance on a button that says 'donate now' or 'go to checkout'.

When the user clicks on that link they will be send to a payment portal. That looks like this. The URL expires in 20 minutes and once the user pays they will be redirected to the redirect URL that we provided.

<figure><img src="/files/eSiuAB7TYbZ22oUg6DOB" alt=""><figcaption></figcaption></figure>

{% openapi src="/files/xJ3v0GinyfONRbxz8Srh" path="/user/{userID}/monetary-account/{monetary-accountID}/bunqme-tab/{itemId}" method="get" %}
[swagger.json](https://346554585-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FGE9Y1hc6C24r4Hen6KFH%2Fuploads%2Fgit-blob-218d6e618d396b5e315e30f903e92e143f7a4cc1%2Fswagger.json?alt=media)
{% endopenapi %}

## Example response that shows the bunqme\_tab\_share\_url

```json

{
    "Response": [
        {
            "BunqMeTab": {
                "id": 7602,
                "created": "2025-04-16 11:34:47.843460",
                "updated": "2025-04-16 11:34:47.843460",
                "time_expiry": "2025-05-16 11:34:47.843107",
                "monetary_account_id": 2079522,
                "status": "WAITING_FOR_PAYMENT",
                "type": "BUNQ_ME",
                "alias_monetary_account": {
                    "iban": "NL53BUNQ2090359587",
                    "is_light": false,
                    "display_name": "N. Carter",
                    "avatar": {...},
                    "label_user": {... },
                "bunqme_tab_share_url": "https://bunqme.triage.bunq.net/o/6kwsisQcpUD4hWaBZUnuW4",
                "bunqme_tab_entry": {
                    "uuid": "de05a8a3-3257-4e73-a004-af3638251e5c",
                    "created": "2025-04-16 11:34:47.887847",
                    "updated": "2025-04-16 11:34:47.887847",
                    "amount_inquired": {
                        "currency": "EUR",
                        "value": "1.00"
                    },
                    "status": "WAITING_FOR_PAYMENT",
                    "description": "Donation",
                    "alias": {
                        "iban": "NL53BUNQ2090359587",
                        "is_light": false,
                        "display_name": "N. Carter",
                        "avatar": {
                            "uuid": "419bbe21-52d7-4037-8787-a57e4c649049",
                            "image": [
                                {
                                    "attachment_public_uuid": "ef1a52ce-6b3a-4357-b716-de91948b831d",
                                    "height": 1023,
                                    "width": 1024,
                                    "content_type": "image/png",
                                    "urls": [
                                        {
                                            "type": "ORIGINAL",
                                            "url": "https://bunq-triage-model-storage-public.s3.eu-central-1.amazonaws.com/bunq_file/File/content/921ece497cd00f4e0cef3f0f63a962c31cf3f8e35311d127d5a7b23be3d074d5.png"
                                        }
                                    ]
                                }
                            ],
                            "anchor_uuid": null,
                            "style": "NONE"
                        },
                        "label_user": {
                            "uuid": "5f33833d-f416-428b-a654-d3b25bb3bc9d",
                            "display_name": "N. Carter",
                            "country": "NL",
                            "avatar": {
                                "uuid": "9debf55b-70b1-4d43-8d51-d6e7bd42fb5b",
                                "image": [
                                    {
                                        "attachment_public_uuid": "4b7e0d1d-9167-48ac-990a-70e342c87812",
                                        "height": 126,
                                        "width": 200,
                                        "content_type": "image/jpeg",
                                        "urls": [
                                            {
                                                "type": "ORIGINAL",
                                                "url": "https://bunq-triage-model-storage-public.s3.eu-central-1.amazonaws.com/bunq_file/File/content/6979a145b7ea9ecc3459358122cb560608f02d36d4b8cd6b770f50e36aa35512.jpg"
                                            }
                                        ]
                                    }
                                ],
                                "anchor_uuid": "5f33833d-f416-428b-a654-d3b25bb3bc9d",
                                "style": "NONE"
                            },
                            "public_nick_name": "Nicola",
                            "type": "PERSON"
                        },
                        "country": "NL"
                    },
                    "redirect_url": "https://bunq.com",
                    "merchant_available": [
                        {
                            "merchant_type": "IDEAL",
                            "available": true
                        },
                        {
                            "merchant_type": "SOFORT",
                            "available": true
                        },
                        {
                            "merchant_type": "CHECKOUT",
                            "available": true
                        },
                        {
                            "merchant_type": "BANCONTACT",
                            "available": true
                        },
                        {
                            "merchant_type": "BUNQ",
                            "available": true
                        }
                    ],
                    "invite_profile_name": "NicolaLilacGorilla"
                },
                "bunqme_tab_entries": [
                    {
                        "id": 7479,
                        "created": "2025-04-16 11:34:47.887847",
                        "updated": "2025-04-16 11:34:47.887847",
                        "amount_inquired": {
                            "currency": "EUR",
                            "value": "1.00"
                        },
                        "status": "WAITING_FOR_PAYMENT",
                        "description": "Donation",
                        "alias": {
                            "iban": "NL53BUNQ2090359587",
                            "is_light": false,
                            "display_name": "N. Carter",
                            "avatar": {
                                "uuid": "419bbe21-52d7-4037-8787-a57e4c649049",
                                "image": [
                                    {
                                        "attachment_public_uuid": "ef1a52ce-6b3a-4357-b716-de91948b831d",
                                        "height": 1023,
                                        "width": 1024,
                                        "content_type": "image/png",
                                        "urls": [
                                            {
                                                "type": "ORIGINAL",
                                                "url": "https://bunq-triage-model-storage-public.s3.eu-central-1.amazonaws.com/bunq_file/File/content/921ece497cd00f4e0cef3f0f63a962c31cf3f8e35311d127d5a7b23be3d074d5.png"
                                            }
                                        ]
                                    }
                                ],
                                "anchor_uuid": null,
                                "style": "NONE"
                            },
                            "label_user": {
                                "uuid": "5f33833d-f416-428b-a654-d3b25bb3bc9d",
                                "display_name": "N. Carter",
                                "country": "NL",
                                "avatar": {
                                    "uuid": "9debf55b-70b1-4d43-8d51-d6e7bd42fb5b",
                                    "image": [
                                        {
                                            "attachment_public_uuid": "4b7e0d1d-9167-48ac-990a-70e342c87812",
                                            "height": 126,
                                            "width": 200,
                                            "content_type": "image/jpeg",
                                            "urls": [
                                                {
                                                    "type": "ORIGINAL",
                                                    "url": "https://bunq-triage-model-storage-public.s3.eu-central-1.amazonaws.com/bunq_file/File/content/6979a145b7ea9ecc3459358122cb560608f02d36d4b8cd6b770f50e36aa35512.jpg"
                                                }
                                            ]
                                        }
                                    ],
                                    "anchor_uuid": "5f33833d-f416-428b-a654-d3b25bb3bc9d",
                                    "style": "NONE"
                                },
                                "public_nick_name": "Nicola",
                                "type": "PERSON"
                            },
                            "country": "NL"
                        },
                        "redirect_url": "https://bunq.com"
                    }
                ],
                "result_inquiries": []
            }
        }
    ]
}




```

As we can see our URL Will be: "<https://bunqme.triage.bunq.net/o/6kwsisQcpUD4hWaBZUnuW4>", So we can add that to our own webpage. Sending the user to that page brings them to this page where they can make their payment.

{% hint style="warning" %}

## Be aware the URL to your tab eventually expires

It's good to know that bunq.me-tab links eventually expire. The bunqmetab object has a property **time\_expiry** that tells you when.

```json
{
    "Response": [
        {
            "BunqMeTab": {
                "id": 7602,
                "created": "2025-04-16 11:34:47.843460",
                "updated": "2025-04-16 11:40:34.412358",
                "time_expiry": "2025-05-16 11:34:47.843107",
                other properties...
                },
        }]
```

```json
}


```

{% endhint %}

## The Redirect to your thank you page

This is already set up. When you made the initial POST request to set up your bunq.me tab you added a redirect\_url and you can see it in the response. So when the user pays, they will be automatically forwarded to bunq.com which I specified in the initial POST request.

{% hint style="success" %}
If you want to retrieve the user of **your** system to the thank you page (or any redirect page for that matter) then you may want to construct a bunqme-tab for each individual user. Let's assume your system keeps track of a userid then you could construct a redirect URL like so:

* <https://redirecturl.com/checkout\\_completed/{userid\\_of\\_your\\_user}>
* This allows you to retrieve the userdetails from your database and enable a subscription, or send them an email as a result.
  {% endhint %}

## How do I get a list of payments?

If you wonder how you got paid they you can always check the payments in your monetary account. But there is a much easier way. Simply by doing another GET request on the /bunqme-tab endpoint gets you the object that we already showed above.


# How to manage your cards


# Introduction

## How to Temporarily Share Your bunq Card with a Friend (With Limits & PIN Control)

Have you ever wanted to lend your bunq card to a friend but still maintain full control over spending? With the bunq API, you can dynamically set spending limits, change the PIN code, and even revert everything back to normal once your friend is done using the card. So that's what we'll focus on in this tutorial. To give you a grasp on what is possible using our API.

In this tutorial, we’ll build a simple application that:

* Temporarily assigns your card to a friend’s account
* Sets a spending limit to prevent overspending
* Changes the PIN code for security
* Restores your original settings once the card is returned

By the end of this guide, you’ll understand how to programmatically control your bunq card with the API, ensuring security and flexibility. Step 1 is ordering a card, Let’s dive in!


# Ordering a card

So we want to lend out our bunq card to a friend but still maintain full control over spending. The first thing we need is, in fact a card. So let's order one.

## What card do we want?

There are several different types of card available within bunq. You can check out our [Cards](/api-reference/cards) section in the API reference. The first pick is between [Card Credit](/api-reference/cards/card-credit) or [Card Debit](/api-reference/cards/card-debit). Once you have chosen one we can start constructing the API call to order a card. Pay attention to the Alias that the card is connected to. This is the pointer to the monetary account that will be connected at first with the card. Its IBAN code is also the one that will be printed on the card itself. The pointer must be of type IBAN.

For this example we'll use the credit card API endpoint like so:

{% openapi src="/files/xJ3v0GinyfONRbxz8Srh" path="/user/{userID}/card-credit" method="post" %}
[swagger.json](https://346554585-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FGE9Y1hc6C24r4Hen6KFH%2Fuploads%2Fgit-blob-218d6e618d396b5e315e30f903e92e143f7a4cc1%2Fswagger.json?alt=media)
{% endopenapi %}

#### Now once we have created the card we can move on to the next step: setting a pin code and a limit for our friend. So that they can only use the card 1x within those limits. Let's go to setting Limits and updating the pin


# Setting the card Limit and changing the PIN code

In the last part we ordered a card that we want to lend to our friend. For this tutorial we want him to only make 1 purchase of maximum €100 and our friend can pick his own pincode.

Doing this is very simple using the PUT method on our cards endpoint.

We only have to pass the values we want to change. In our case we will chose a body like so:<br>

```json
{
    "pin_code": "1234",
    "card_limit": {
    "value": "100.00",
    "currency": "EUR"
    },
    "card_limit_atm": {
        "value": "0.00",
        "currency": "EUR"
    }
}
```

This sets our pincode to 1234 (assuming that's the code our friend wants). And we set the limit on the card to €100 , and €0 for ATM withdrawels so our friend does not grab a bunch of cash. If you want to change other values (e.g. allowed countries or the monetary account connected then we can but it's outside of the scope for this tutorial).

### Here is the the request with all parameters

{% openapi src="/files/xJ3v0GinyfONRbxz8Srh" path="/user/{userID}/card/{itemId}" method="put" %}
[swagger.json](https://346554585-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FGE9Y1hc6C24r4Hen6KFH%2Fuploads%2Fgit-blob-218d6e618d396b5e315e30f903e92e143f7a4cc1%2Fswagger.json?alt=media)
{% endopenapi %}

## Now Let's assume our friend made his card payment, how will we know?

After our friend made his card payment we would like to revert back to our own spending limits and our own pin code. We can do this simply by redoing the API call we just did and updating the body. So

```json
{
    "pin_code": "your pin code",
    "card_limit": {
    "value": "your preferred limit",
    "currency": "EUR"
    },
    "card_limit_atm": {
        "value": "your ATM limit",
        "currency": "EUR"
    }
}
```

The issue is more, how will we know when to make that call, as in the ideal situation for this tutorial we want to make it as soon as the card payment comes in. As with everything at bunq we can do this near instant.

The crude way of doing it is by polling our payments with the GET [Payment](/payment)method, that returns a list of payments. There is a more sophisticated design pattern we could use which is using Callbacks. To set up callbacks we need to register a notification filter [Notification Filter](/notification-filter)

## Setting up a callback so we know when card transactions are made

Your app can subscribe to events that happen on the bunq API by using [Callbacks (Webhooks)](/basics/callbacks-webhooks). These are POST requests our server will make towards your server when an event happens.

One of the events we could listen to is `CARD_TRANSACTION_SUCCESSFUL` which will tell us about succesful card transactions. This is done by a POST request from bunq to a URL we specify (usually on our server).

To do this we can add a callback url by adding a notification filter url. And passing a body like:<br>

```json

{
    "notification_filters": [
        {"category": "CARD_TRANSACTION_SUCCESSFUL",
        "notification_target": "THE URL OF YOUR SERVER THAT WILL PROCESS THE POST REQUEST"}
    ]
}

```

This registers the URL and now bunq will call that endpoint everytime there's a successful card transaction. Your server will have to to check the `card_id`in the event to match it to the card ID that we borrowed our friend.

But if that matches you can simply make the API call that we did before, and revert to the pin\_code and card\_limits of your choice. And the card will be unusable by your friend after that.

## API Call and response for the notificiation filter

{% openapi src="/files/xJ3v0GinyfONRbxz8Srh" path="/user/{userID}/notification-filter-url" method="post" %}
[swagger.json](https://346554585-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FGE9Y1hc6C24r4Hen6KFH%2Fuploads%2Fgit-blob-218d6e618d396b5e315e30f903e92e143f7a4cc1%2Fswagger.json?alt=media)
{% endopenapi %}

### The response of such a callback looks like this:

]

```
{
  "NotificationUrl": {
    "target_url": "https://webhook.site/994964bb-7a4c-4be3-836a-da65231b907d",
    "category": "CARD_TRANSACTION_SUCCESSFUL",
    "event_type": "CARD_PAYMENT_ALLOWED",
    "object": {
      "MasterCardAction": {
        "id": 491965,
        "created": "2025-04-09 14:14:27.584029",
        "updated": "2025-04-09 14:14:28.483979",
        "monetary_account_id": 2078790,
        "card_id": 449304,
        "card_authorisation_id_response": "FHF728",
        "amount_local": {
          "currency": "EUR",
          "value": "1.00"
        },
        "amount_converted": {
          "currency": "EUR",
          "value": "1.00"
        },
        "amount_billing": {
          "currency": "EUR",
          "value": "1.00"
        },
        "amount_original_local": {
          "currency": "EUR",
          "value": "1.00"
        },
        "amount_original_billing": {
          "currency": "EUR",
          "value": "1.00"
        },
        "amount_fee": {
          "currency": "EUR",
          "value": "0.00"
        },
        "decision": "ALLOWED",
        "payment_status": "PAYMENT_SUCCESSFUL_PENDING_SETTLEMENT",
        "decision_description": "Card transaction is successfully authorised. Your money has been reserved for the merchant to claim.",
        "decision_description_translated": "Card transaction is successfully authorized. Your money has been reserved for the merchant to claim.",
        "decision_together_url": "https://together.bunq.com/d/16793-what-does-the-status-of-my-card-payment-mean",
        "description": "CCV*KA Sloterdijk Amsterdam, NL\n",
        "authorisation_status": "AUTHORISED",
        "authorisation_type": "NORMAL_AUTHORISATION",
        "settlement_status": "OPEN",
        "clearing_status": "PENDING",
        "maturity_date": "2025-05-01",
        "city": "Amsterdam",
        "alias": {
          "iban": "NL52BUNQ2090374640",
          "is_light": false,
          "display_name": "A. Luu",
          "avatar": {
            "uuid": "27234277-30d4-46c8-80cf-d975a6c6b21d",
            "image": [
              {
                "attachment_public_uuid": "60a6d2d5-5080-4c02-a2b6-52c8beeafd56",
                "height": 1023,
                "width": 1024,
                "content_type": "image/png",
                "urls": [
                  {
                    "type": "ORIGINAL",
                    "url": "https://bunq-triage-model-storage-public.s3.eu-central-1.amazonaws.com/bunq_file/File/content/921ece497cd00f4e0cef3f0f63a962c31cf3f8e35311d127d5a7b23be3d074d5.png"
                  }
                ]
              }
            ],
            "anchor_uuid": null,
            "style": "NONE"
          },
          "label_user": {
            "uuid": "a3f43087-9139-47ec-9453-0609afc0dbdd",
            "display_name": "A. Luu",
            "country": "NL",
            "avatar": {
              "uuid": "9debf55b-70b1-4d43-8d51-d6e7bd42fb5b",
              "image": [
                {
                  "attachment_public_uuid": "4b7e0d1d-9167-48ac-990a-70e342c87812",
                  "height": 126,
                  "width": 200,
                  "content_type": "image/jpeg",
                  "urls": [
                    {
                      "type": "ORIGINAL",
                      "url": "https://bunq-triage-model-storage-public.s3.eu-central-1.amazonaws.com/bunq_file/File/content/6979a145b7ea9ecc3459358122cb560608f02d36d4b8cd6b770f50e36aa35512.jpg"
                    }
                  ]
                }
              ],
              "anchor_uuid": "a3f43087-9139-47ec-9453-0609afc0dbdd",
              "style": "EASY_GREEN"
            },
            "public_nick_name": "Ada",
            "type": "PERSON"
          },
          "country": "NL"
        },
        "counterparty_alias": {
          "iban": null,
          "is_light": null,
          "display_name": "Koffie Academie",
          "avatar": null,
          "label_user": {
            "uuid": null,
            "display_name": "Koffie Academie",
            "country": "NL",
            "avatar": null,
            "public_nick_name": "Koffie Academie",
            "type": null
          },
          "country": "NL",
          "merchant_category_code": "5411"
        },
        "label_card": {
          "uuid": "dee4bf0a-8f9c-4387-be82-db0b4d02c4d5",
          "type": "MASTERCARD",
          "second_line": "",
          "expiry_date": "2029-04-30",
          "status": "ACTIVE",
          "label_user": {
            "uuid": "a3f43087-9139-47ec-9453-0609afc0dbdd",
            "display_name": "Ada",
            "country": "000",
            "avatar": {
              "uuid": "9debf55b-70b1-4d43-8d51-d6e7bd42fb5b",
              "image": [
                {
                  "attachment_public_uuid": "4b7e0d1d-9167-48ac-990a-70e342c87812",
                  "height": 126,
                  "width": 200,
                  "content_type": "image/jpeg",
                  "urls": [
                    {
                      "type": "ORIGINAL",
                      "url": "https://bunq-triage-model-storage-public.s3.eu-central-1.amazonaws.com/bunq_file/File/content/6979a145b7ea9ecc3459358122cb560608f02d36d4b8cd6b770f50e36aa35512.jpg"
                    }
                  ]
                }
              ],
              "anchor_uuid": "a3f43087-9139-47ec-9453-0609afc0dbdd",
              "style": "EASY_GREEN"
            },
            "public_nick_name": "Ada",
            "type": "PERSON"
          },
          "label_user_owner": {
            "uuid": "a3f43087-9139-47ec-9453-0609afc0dbdd",
            "display_name": "Ada",
            "country": "000",
            "avatar": {
              "uuid": "9debf55b-70b1-4d43-8d51-d6e7bd42fb5b",
              "image": [
                {
                  "attachment_public_uuid": "4b7e0d1d-9167-48ac-990a-70e342c87812",
                  "height": 126,
                  "width": 200,
                  "content_type": "image/jpeg",
                  "urls": [
                    {
                      "type": "ORIGINAL",
                      "url": "https://bunq-triage-model-storage-public.s3.eu-central-1.amazonaws.com/bunq_file/File/content/6979a145b7ea9ecc3459358122cb560608f02d36d4b8cd6b770f50e36aa35512.jpg"
                    }
                  ]
                }
              ],
              "anchor_uuid": "a3f43087-9139-47ec-9453-0609afc0dbdd",
              "style": "EASY_GREEN"
            },
            "public_nick_name": "Ada",
            "type": "PERSON"
          },
          "label_user_holder": {
            "uuid": "a3f43087-9139-47ec-9453-0609afc0dbdd",
            "display_name": "A. Luu",
            "country": "NL",
            "avatar": {
              "uuid": "9debf55b-70b1-4d43-8d51-d6e7bd42fb5b",
              "image": [
                {
                  "attachment_public_uuid": "4b7e0d1d-9167-48ac-990a-70e342c87812",
                  "height": 126,
                  "width": 200,
                  "content_type": "image/jpeg",
                  "urls": [
                    {
                      "type": "ORIGINAL",
                      "url": "https://bunq-triage-model-storage-public.s3.eu-central-1.amazonaws.com/bunq_file/File/content/6979a145b7ea9ecc3459358122cb560608f02d36d4b8cd6b770f50e36aa35512.jpg"
                    }
                  ]
                }
              ],
              "anchor_uuid": "a3f43087-9139-47ec-9453-0609afc0dbdd",
              "style": "EASY_GREEN"
            },
            "public_nick_name": "Ada",
            "type": "PERSON"
          },
          "label_primary_account_number": {
            "uuid": "d1e42007-a01e-497a-a88d-c40ca7abf154",
            "description": "",
            "last_four_digit": "8069",
            "mastercard_product": "MCS"
          }
        },
        "merchant_id": "21914428",
        "token_status": null,
        "reservation_expiry_time": null,
        "clearing_expiry_time": "2025-04-16 14:14:27.584029",
        "pan_entry_mode_user": "ICC",
        "secure_code_id": null,
        "wallet_provider_id": "420",
        "request_reference_split_the_bill": [],
        "card_tokenization_event": null,
        "all_auto_save_entry": [],
        "all_mastercard_action_refund": [],
        "applied_limit": "CARD_LIMIT_POS_ICC",
        "tree": null,
        "pos_card_presence": "0",
        "pos_card_holder_presence": "0",
        "eligible_whitelist_id": null,
        "cashback_payout_item": null,
        "point_mutation": {
          "id": 203039,
          "created": "2025-04-09 14:14:29.755610",
          "updated": "2025-04-09 14:14:29.755610",
          "number_of_point": 2
        },
        "blacklist": null,
        "transaction_dispute_available": "NONE",
        "additional_authentication_status": "NONE",
        "pin_status": "VALID_ONLINE",
        "mastercard_action_report": null,
        "merchant_category_code": "5411",
        "company_employee_card_receipt": null
      }
    }
  }
}
```


# Access end user acount information through OAuth

### Goal of this tutorial <a href="#oauth-authentication" id="oauth-authentication"></a>

In this tutorial we'll walk you through the steps to get access to a bunq users bank account. We'll register your application, create a OAuth client and set it up in such a way so that:

* You can generate a QR code - this QR code can be used by a bunq user to connect to the app you're building
* How to set up that connection between the bunq user and your app
* Retrieve end-user information

For OAuth to work there are 3 parties involved

* The bunq user - that is likely also a user of your app
* Your app - that is trying to access information about the end users' bunq account
* bunq - which stores all the information in our database and that your app wants to access on behalf of a end user.

{% hint style="info" %}
A common example for a usecase with oauth is if your app is an accounting tool. You want to be able to read transactions on the account of the end user. Connecting through oauth helps you get this set up.
{% endhint %}

At the end of this tutorial you'll have:

* An app with oauth client
* A way to retrieve payments on behalf of other users

### What is OAuth <a href="#oauth-authentication" id="oauth-authentication"></a>

OAuth 2.0 is an industry-standard protocol that allows third-party applications to securely access bunq user accounts without exposing credentials. This method enables seamless authorization while maintaining user control over permissions.6

{% hint style="danger" %}
**Important Note** If you, as a developer, intend to access data belonging to **other bunq users** (e.g., account information, transaction history, or initiate payments on their behalf), you must ensure that you:

* ✅ **Fully comply with** [**bunq’s Terms and Conditions**](https://static.bunq.com/framer/documents/Terms-Conditions-API-EN-20200805.pdf) for API usage.
* ✅ **Obtain explicit user consent** as required.
* ⚖️ **May be subject to** [**PSD2 regulations**](https://eur-lex.europa.eu/legal-content/EN/LSU/?uri=oj:JOL_2015_337_R_0002), including registration as an AISP (Account Information Service Provider) or PISP (Payment Initiation Service Provider) with your national competent authority.

Misuse or non-compliance may lead to access being revoked or legal consequences.

### &#x20;<a href="#oauth-authentication" id="oauth-authentication"></a>

{% endhint %}

### What is next

Next up we'll get started with setting up the OAuth client.


# Setting up the Oauth Client

For this tutorial we want to read the transactions on behalf of a end user. Te first thing we need to do is register a oauth client. There are 2 ways to do this and we'll dive into both of them. In this chapter we try to register the client and retrieve the client ID and Client Secret that belong to your oauth client.

## 1. Register oauth client as a bunq user

If you have access to a bunq account then it's fairly straight forward to set up an oauth client.

You can simply retrieve your oauth client ID and Secret from the bunq app. This is also the easiest way to set up a redirect URL (More on this later).

<figure><img src="/files/KhnPHBQpF7K4sEfgkhlN" alt="" width="375"><figcaption><p>Screen in bunq app where you can set up your oauth client</p></figcaption></figure>

Another way is to register the client on the [Broken mention](broken://pages/glvIAX6lW191kCjCcygI).

{% hint style="info" %}
End users will see your (business) name when they grant access to their bank account . That looks like this:

<img src="https://private-user-images.githubusercontent.com/71013416/447534152-cfea9651-f5e7-4286-9cc8-36282913a18b.png?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3NTA2OTA3NTQsIm5iZiI6MTc1MDY5MDQ1NCwicGF0aCI6Ii83MTAxMzQxNi80NDc1MzQxNTItY2ZlYTk2NTEtZjVlNy00Mjg2LTljYzgtMzYyODI5MTNhMThiLnBuZz9YLUFtei1BbGdvcml0aG09QVdTNC1ITUFDLVNIQTI1NiZYLUFtei1DcmVkZW50aWFsPUFLSUFWQ09EWUxTQTUzUFFLNFpBJTJGMjAyNTA2MjMlMkZ1cy1lYXN0LTElMkZzMyUyRmF3czRfcmVxdWVzdCZYLUFtei1EYXRlPTIwMjUwNjIzVDE0NTQxNFomWC1BbXotRXhwaXJlcz0zMDAmWC1BbXotU2lnbmF0dXJlPWYxOGViYTE3Njg1NThiOWE1ZWNiZjE1NWY5YWMyNmU2ODA1YWYxODg4N2U3MmVjNzU4ODg2NjhiYTVmMDRlZWEmWC1BbXotU2lnbmVkSGVhZGVycz1ob3N0In0.t6eSsjwMAJ-H5Pi-LWs1XgbEeJqhyDs6YFsizXN0Bk8" alt="" data-size="original">
{% endhint %}

## 2. Register as a PSD2 user

In some cases you may not be a bunq user yourself but still want access to bunq user data. For instance when you're a PSD2-certified third-party provider (TPP), you can integrate directly with the bunq Public API using your **eIDAS certificate**.

This guide walks you through the step-by-step process to register and authenticate with the bunq environment.

<br>

This method to register a oauth client may not be applicable to you, if you're unsure refer to [PSD2](/psd2/are-you-a-third-party-provider-tpp-start-here). and [Are you a Third Party Provider (TPP)? Start here!](/psd2/are-you-a-third-party-provider-tpp-start-here)


# Hidden references


# Company

{% openapi src="<https://raw.githubusercontent.com/bunq/doc/refs/heads/develop/swagger.json>" path="/user/{userID}/company" method="post" %}
<https://raw.githubusercontent.com/bunq/doc/refs/heads/develop/swagger.json>
{% endopenapi %}

{% openapi src="<https://raw.githubusercontent.com/bunq/doc/refs/heads/develop/swagger.json>" path="/user/{userID}/company" method="get" %}
<https://raw.githubusercontent.com/bunq/doc/refs/heads/develop/swagger.json>
{% endopenapi %}

{% openapi src="<https://raw.githubusercontent.com/bunq/doc/refs/heads/develop/swagger.json>" path="/user/{userID}/company/{itemId}" method="get" %}
<https://raw.githubusercontent.com/bunq/doc/refs/heads/develop/swagger.json>
{% endopenapi %}

{% openapi src="<https://raw.githubusercontent.com/bunq/doc/refs/heads/develop/swagger.json>" path="/user/{userID}/company/{itemId}" method="put" %}
<https://raw.githubusercontent.com/bunq/doc/refs/heads/develop/swagger.json>
{% endopenapi %}


# Challenge Request

{% openapi src="<https://raw.githubusercontent.com/bunq/doc/refs/heads/develop/swagger.json>" path="/user/{userID}/challenge-request/{itemId}" method="get" %}
<https://raw.githubusercontent.com/bunq/doc/refs/heads/develop/swagger.json>
{% endopenapi %}

{% openapi src="<https://raw.githubusercontent.com/bunq/doc/refs/heads/develop/swagger.json>" path="/user/{userID}/challenge-request/{itemId}" method="put" %}
<https://raw.githubusercontent.com/bunq/doc/refs/heads/develop/swagger.json>
{% endopenapi %}


# Credential Password IP

{% openapi src="<https://raw.githubusercontent.com/bunq/doc/refs/heads/develop/swagger.json>" path="/user/{userID}/credential-password-ip/{itemId}" method="get" %}
<https://raw.githubusercontent.com/bunq/doc/refs/heads/develop/swagger.json>
{% endopenapi %}

{% openapi src="<https://raw.githubusercontent.com/bunq/doc/refs/heads/develop/swagger.json>" path="/user/{userID}/credential-password-ip" method="get" %}
<https://raw.githubusercontent.com/bunq/doc/refs/heads/develop/swagger.json>
{% endopenapi %}

{% openapi src="<https://raw.githubusercontent.com/bunq/doc/refs/heads/develop/swagger.json>" path="/user/{userID}/credential-password-ip/{credential-password-ipID}/ip/{itemId}" method="get" %}
<https://raw.githubusercontent.com/bunq/doc/refs/heads/develop/swagger.json>
{% endopenapi %}

{% openapi src="<https://raw.githubusercontent.com/bunq/doc/refs/heads/develop/swagger.json>" path="/user/{userID}/credential-password-ip/{credential-password-ipID}/ip/{itemId}" method="put" %}
<https://raw.githubusercontent.com/bunq/doc/refs/heads/develop/swagger.json>
{% endopenapi %}

{% openapi src="<https://raw.githubusercontent.com/bunq/doc/refs/heads/develop/swagger.json>" path="/user/{userID}/credential-password-ip/{credential-password-ipID}/ip" method="post" %}
<https://raw.githubusercontent.com/bunq/doc/refs/heads/develop/swagger.json>
{% endopenapi %}

{% openapi src="<https://raw.githubusercontent.com/bunq/doc/refs/heads/develop/swagger.json>" path="/user/{userID}/credential-password-ip/{credential-password-ipID}/ip" method="get" %}
<https://raw.githubusercontent.com/bunq/doc/refs/heads/develop/swagger.json>
{% endopenapi %}

{% openapi src="<https://raw.githubusercontent.com/bunq/doc/refs/heads/develop/swagger.json>" path="/payment-service-provider-credential/{itemId}" method="get" %}
<https://raw.githubusercontent.com/bunq/doc/refs/heads/develop/swagger.json>
{% endopenapi %}

{% openapi src="<https://raw.githubusercontent.com/bunq/doc/refs/heads/develop/swagger.json>" path="/payment-service-provider-credential" method="post" %}
<https://raw.githubusercontent.com/bunq/doc/refs/heads/develop/swagger.json>
{% endopenapi %}


# Feature Announcement

{% openapi src="<https://raw.githubusercontent.com/bunq/doc/refs/heads/develop/swagger.json>" path="/user/{userID}/feature-announcement/{itemId}" method="get" %}
<https://raw.githubusercontent.com/bunq/doc/refs/heads/develop/swagger.json>
{% endopenapi %}


# Certificate Pinned

{% openapi src="<https://raw.githubusercontent.com/bunq/doc/refs/heads/develop/swagger.json>" path="/user/{userID}/certificate-pinned" method="post" %}
<https://raw.githubusercontent.com/bunq/doc/refs/heads/develop/swagger.json>
{% endopenapi %}

{% openapi src="<https://raw.githubusercontent.com/bunq/doc/refs/heads/develop/swagger.json>" path="/user/{userID}/certificate-pinned/{itemId}" method="get" %}
<https://raw.githubusercontent.com/bunq/doc/refs/heads/develop/swagger.json>
{% endopenapi %}

{% openapi src="<https://raw.githubusercontent.com/bunq/doc/refs/heads/develop/swagger.json>" path="/user/{userID}/certificate-pinned/{itemId}" method="delete" %}
<https://raw.githubusercontent.com/bunq/doc/refs/heads/develop/swagger.json>
{% endopenapi %}

{% openapi src="<https://raw.githubusercontent.com/bunq/doc/refs/heads/develop/swagger.json>" path="/user/{userID}/certificate-pinned" method="get" %}
<https://raw.githubusercontent.com/bunq/doc/refs/heads/develop/swagger.json>
{% endopenapi %}


# Start here

***

### bunq API Reference

Welcome to the complete reference for the **bunq API** – your all-in-one guide to everything you can build and automate with our platform. This page contains **over 400 endpoints**, organized **alphabetically**, each with clear descriptions and sample API calls.

Whether you're integrating payments, managing user accounts, automating business workflows, or building new financial experiences, this reference is your go-to resource. From the smallest utility call to the most advanced banking operations, if it's possible with the bunq API, you'll find it here.

Looking for something specific? Use the search or jump to a section — everything is right where you need it.


# Additional Transaction Information Category

Get the available categories.

{% openapi src="<https://raw.githubusercontent.com/bunq/doc/refs/heads/develop/swagger.json>" path="/user/{userID}/additional-transaction-information-category" method="get" %}
<https://raw.githubusercontent.com/bunq/doc/refs/heads/develop/swagger.json>
{% endopenapi %}


# Additional Transaction Information Category User Defined

{% openapi src="<https://raw.githubusercontent.com/bunq/doc/refs/heads/develop/swagger.json>" path="/user/{userID}/additional-transaction-information-category-user-defined" method="post" %}
<https://raw.githubusercontent.com/bunq/doc/refs/heads/develop/swagger.json>
{% endopenapi %}


# Attachment

**Attachments** in the bunq app allow users to attach images or text to payments, making it easy to reference receipts, invoices, or any relevant information later. Each attachment is linked to a Monetary account. Each attachment must have a specific file type, such as JPEG, PNG, or PDF, and the file is first uploaded to a monetary account before it can be linked to any payment.

The process involves multiple steps:

1. **Create the attachment -That is this page-.**\
   Users must first upload the file to the monetary account. This step requires providing the file itself along with a brief description. Bunq stores the attachment securely and returns a unique attachment ID, which will be used in the next step.
2. **Link the attachment to a payment:** Once the attachment exists, it can be linked to a specific payment as a **note-attachment**. This allows users to associate files like receipts or invoices directly with a transaction for easy retrieval. An optional description can also be added to give context to the attachment within the payment. To see how to do this reference [Payment](/note-text-and-attachment/payment)
3. **Add text notes (optional):** Separately, users can also add plain text notes to a payment as a **note-text**, without uploading a file. This is useful for quick comments, explanations, or any textual reference that supplements the payment. [Note Text & Attachment](/note-text-and-attachment)

{% openapi src="<https://raw.githubusercontent.com/bunq/doc/refs/heads/develop/swagger.json>" path="/user/{userID}/monetary-account/{monetary-accountID}/attachment" method="post" %}
<https://raw.githubusercontent.com/bunq/doc/refs/heads/develop/swagger.json>
{% endopenapi %}

{% openapi src="<https://raw.githubusercontent.com/bunq/doc/refs/heads/develop/swagger.json>" path="/user/{userID}/attachment/{itemId}" method="get" %}
<https://raw.githubusercontent.com/bunq/doc/refs/heads/develop/swagger.json>
{% endopenapi %}


# Attachment Public

{% openapi src="<https://raw.githubusercontent.com/bunq/doc/refs/heads/develop/swagger.json>" path="/attachment-public/{attachment-publicUUID}/content" method="get" %}
<https://raw.githubusercontent.com/bunq/doc/refs/heads/develop/swagger.json>
{% endopenapi %}

{% openapi src="<https://raw.githubusercontent.com/bunq/doc/refs/heads/develop/swagger.json>" path="/attachment-public" method="post" %}
<https://raw.githubusercontent.com/bunq/doc/refs/heads/develop/swagger.json>
{% endopenapi %}

{% openapi src="<https://raw.githubusercontent.com/bunq/doc/refs/heads/develop/swagger.json>" path="/attachment-public/{itemId}" method="get" %}
<https://raw.githubusercontent.com/bunq/doc/refs/heads/develop/swagger.json>
{% endopenapi %}




---

[Next Page](/llms-full.txt/1)

