Docs/Lanes Link/Providers/bunq

bunq

Accounts, balances, transaction history, and payments. Read this one first

console
$ lanes link connect bunq --profile personal --workspace local

You are asked for one API key, generated inside the bunq app. There is no web console, no OAuth client, and no browser consent, because bunq does not offer one for your own account.

Read the next two sections before you connect this one. It is the only account here that can move money.

What your agent can do

Eleven operations, and three of them spend.

CapabilityWhat it does
bunq.List_all_UserThe user id every other call is addressed under
bunq.List_all_MonetaryAccount_for_UserAccounts and balances
bunq.List_all_Payment_for_User_MonetaryAccountTransaction history
bunq.READ_Payment_for_User_MonetaryAccountOne transaction
bunq.List_all_DraftPayment_for_User_MonetaryAccountDrafts awaiting approval
bunq.READ_DraftPayment_for_User_MonetaryAccountOne draft
bunq.List_all_PaymentBatch_for_User_MonetaryAccountBatches
bunq.CREATE_Payment_for_User_MonetaryAccountPays. Immediately, and irreversibly
bunq.CREATE_PaymentBatch_for_User_MonetaryAccountPays up to 350 recipients at once
bunq.CREATE_DraftPayment_for_User_MonetaryAccountPrepares a payment for you to approve in the app
bunq.UPDATE_DraftPayment_for_User_MonetaryAccountAccepts or rejects a draft

A direct payment has no confirmation step anywhere. Not in the app, not by email, not here. bunq accepts the call and the money is gone. A draft payment is the same call with a human in the middle: it appears in the bunq app and does nothing until you approve it.

Standing orders, opening and closing accounts, and ordering cards are all reachable in bunq's API and deliberately absent here, so no policy rule can reach them.

Keeping an agent to drafts only

Two bounds, and only one of them is ours.

A spending limit on the API key, set in the bunq app. This is the one that does not depend on any software here being correct: not the policy engine, not the tool list, not this page. Set it, and set it low.

Policy. Connecting with write grants the payment tool, because for an HTTP provider every POST lands in the write bundle. Deny all three:

console
$ lanes link policy deny bunq.CREATE_Payment_for_User_MonetaryAccount \
    bunq.CREATE_PaymentBatch_for_User_MonetaryAccount \
    bunq.UPDATE_DraftPayment_for_User_MonetaryAccount --profile personal --workspace local

The first two are the obvious ones. The third is what makes the other two mean anything: UPDATE_DraftPayment with status: ACCEPTED is how a draft becomes a payment, so an agent left holding it can create a draft and then approve its own draft, and the human checkpoint you bought with the first two lines does not exist. Denying it costs you nothing an agent should have, because accepting, rejecting, and cancelling a draft are all things to do in the bunq app, which is the entire point of a draft.

That leaves reading and draft-making intact, and every payment then waits for you in the app.

The API key

  1. In the bunq app: Profile → Security & Settings → Developers → API keys → Add API key.
  2. Name it Lanes Link, so you can revoke this one later without touching your others.
  3. Set a spending limit on the key. See above.
  4. If this endpoint will ever run anywhere but this machine, mark the key as a wildcard key on the same screen. bunq binds a key to the addresses it has been used from, and a cloud egress address is not stable. The wildcard setting cannot be enabled over the API, deliberately, so this step is yours and nothing can do it for you.
  5. Copy the key and run the connect command above.

You are then asked what to call the connection. bunq publishes no endpoint that reports whose account a key belongs to, so unlike Gmail or GitHub the label is yours to choose.

Connecting does more than store a value: it generates an RSA keypair on your machine, registers the public half with bunq, and stores the key, the private half, and bunq's own public key together under bunq/<connection>. The registration step is what rejects a wrong key, so a bad paste fails there, with bunq's own message, before anything is written to your config. Every request afterwards is signed, and bunq's replies are checked against its key.

Try it against the sandbox first

bunq runs a public sandbox that needs no bank account, and nothing that happens there is real. A sandbox key comes from bunq's tinker flow; see doc.bunq.com.

Copy the vendored spec next to a manifest of your own in <workspace>/data/<profile>/providers.d/. A relative openapi: resolves against the manifest's own directory, so keeping the two together is what makes the path work wherever the workspace is:

YAML
id: bunq_sandbox
name: bunq (sandbox)
connector:
  kind: http
  base_url: https://public-api.sandbox.bunq.com/v1
  openapi: ./bunq.v1.json
auth:
  kind: strategy
  strategy: bunq

There is no sandbox flag, on purpose. The strategy reads its host from base_url, so this manifest handshakes and pays against the sandbox and the built-in bunq does neither. A flag beside base_url would be a second thing to keep true, and getting it wrong would open a session against the sandbox and spend it against production.

When it stops working

What you seeWhat it is
bunq refused the session, or a 401 then successWorking as intended. A session lasts as long as your account's auto-logout setting, a week by default. The call after the failure opens a new one
Every call refused after a deploy or an ISP changeThe key is bound to addresses it has been used from. Mark it as a wildcard key in the app
answered 429bunq rate-limits, and /session-server hardest at one call per thirty seconds. Ordinary calls are 3 GETs and 5 POSTs per three seconds
Anything elseGenerate a new key in the app and run lanes link connect bunq --replace

What is recorded

Account and payment identifiers are kept, and amounts and counterparty details are withheld. Capabilities come from an OpenAPI document vendored into Lanes Link, so the surface is reviewable in a diff and a change upstream cannot widen what your agents can reach without a commit here.

Given that this provider can move money, lanes link audit tail --denied-only is worth a habit. See the audit log.


Next: every provider, or Add it to your agent.