Skip to main content
This guide walks the Finance API end-to-end — from creating your first party to settling a transfer. Every step uses real request and response shapes. Run it against staging and you’ll have a working integration.
Base URL (staging): https://api-staging.venlyfinance.com/v1 Auth: OAuth2 Bearer token in the Authorization header. Tokens expire after 5 minutes — see the Authentication guide.

What you’ll build

  1. Onboard a party (your customer) — created ACTIVE immediately
  2. Open an account for them (its custodial wallet is provisioned automatically)
  3. Get the account verified by a Venly admin
  4. Issue a EUR IBAN so they can fund via SEPA
  5. Send an outbound transfer
The big gate to design around: an account is created unverified (kycStatus: VERIFICATION_PENDING) and a Venly admin must verify it before it can move money. A party, by contrast, is ACTIVE right away. See Account verification.

Prerequisites

1

Staging credentials

A client_id and client_secret from your Venly contact. Staging calls don’t move real money.
2

An HTTP client

All examples use curl. Postman, HTTPie, or any SDK works equally well.
3

A UUID generator

Money-moving endpoints require an idempotencyKey (UUID v4) — uuidgen, [guid]::NewGuid(), or crypto.randomUUID().

Step 1 — Authenticate

Exchange your client credentials for a Bearer token. Every subsequent call needs it in the Authorization header.
curl -X POST https://login-staging.venly.io/auth/realms/VenlyFinance/protocol/openid-connect/token \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "grant_type=client_credentials" \
  -d "client_id=YOUR_CLIENT_ID" \
  -d "client_secret=YOUR_CLIENT_SECRET"
Response
{ "access_token": "eyJhbGciOi...", "expires_in": 300, "token_type": "Bearer" }
Cache the token. Refresh on 401 Unauthorized or a few seconds before expires_in elapses — don’t request a new token on every call.

Step 2 — Create a party

A party is the person or organisation behind an account — INDIVIDUAL (natural person) or ORGANISATION (company). It’s created ACTIVE immediately; there’s no verification step for a party.
curl -X POST https://api-staging.venlyfinance.com/v1/parties \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "partyType": "INDIVIDUAL",
    "externalId": "user-12345",
    "firstName": "Jane",
    "lastName": "Doe",
    "address": { "addressLine1": "1 Example Street", "city": "Amsterdam", "postalCode": "1011AB", "country": "NL" }
  }'
Response (201)
{
  "success": true,
  "result": {
    "id": "7e3b9c2a-1f4d-4a8b-9c11-2d6e8f0a1b22",
    "externalId": "user-12345",
    "partyType": "INDIVIDUAL",
    "status": "ACTIVE",
    "firstName": "Jane",
    "lastName": "Doe",
    "createdAt": "2026-01-15T09:30:00",
    "updatedAt": "2026-01-15T09:30:00",
    "version": 0
  }
}
For an organisation, swap the body:
{ "partyType": "ORGANISATION", "externalId": "org-67890", "name": "Acme B.V.", "vatNumber": "NL123456789B01" }

Step 3 — Open an account

An account holds the wallet, IBANs, and transfer history. Link the party from Step 2 with partyId (it becomes the ACCOUNT_HOLDER). The custodial wallet is provisioned automatically — there’s no separate “create wallet” call. The only difference between company types is the wallet address:
  • Venly-managed — Venly creates and holds the wallet. Omit address.
  • Self-custody — the customer controls the wallet. Send their address (required).
curl -X POST https://api-staging.venlyfinance.com/v1/accounts \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "externalId": "acct-12345",
    "name": "Jane Doe — Main",
    "chain": "BASE",
    "partyId": "7e3b9c2a-1f4d-4a8b-9c11-2d6e8f0a1b22"
  }'
Response (201)
{
  "success": true,
  "result": {
    "id": "b2a1f0e9-8c7d-4e3a-9f21-0a1b2c3d4e5f",
    "externalId": "acct-12345",
    "name": "Jane Doe — Main",
    "kycStatus": "VERIFICATION_PENDING",
    "status": "ACTIVE",
    "createdAt": "2026-01-15T09:30:00",
    "version": 0
  }
}
Set a meaningful externalId — your own user/customer ID. It doubles as a receiver shortcut in transfers (receiverExternalId), so you don’t have to look up the Venly UUID every time.

Add a second account holder (optional)

curl -X POST https://api-staging.venlyfinance.com/v1/accounts/{accountId}/party-roles \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "partyId": "ANOTHER_PARTY_UUID", "roleType": "ACCOUNT_HOLDER" }'

Step 4 — Get the account verified

The account is created with kycStatus: VERIFICATION_PENDING. A Venly admin reviews and verifies it — there’s no API call to trigger this. Once approved, kycStatus becomes VERIFIED. Poll the account until it flips:
curl https://api-staging.venlyfinance.com/v1/accounts/{accountId} \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN"
Response (200)
{ "success": true, "result": { "id": "b2a1f0e9-...", "kycStatus": "VERIFIED", "status": "ACTIVE", "version": 1 } }
Until then, creating IBANs, transfers, payment requests, and pay-in sessions all fail with a KYC error — the exact code and HTTP status vary by operation (e.g. 400 kyc-not-verified for IBANs, 422 kyc-required for pay-in sessions). See Account verification for the full list of what’s gated.

Step 5 — View the wallet

The wallet was created with the account. Read it (and its balances) any time:
curl https://api-staging.venlyfinance.com/v1/accounts/{accountId}/wallets \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN"
Response (200)
{
  "success": true,
  "result": [
    {
      "id": "9f8e7d6c-5b4a-4938-8271-6a5b4c3d2e1f",
      "chain": "BASE",
      "type": "SELF_CUSTODY",
      "address": "0x71C7656EC7ab88b098defB751B7401B5f6d8976F",
      "balances": [
        { "asset": "USDC", "contractAddress": "0x036CbD53842c5426634e7929541eC2318f3dCF7e", "amount": { "total": "0", "available": "0", "reserved": "0" } }
      ],
      "amlStatus": "PENDING"
    }
  ]
}
Balance amounts are decimal strings. type is VENLY_MANAGED or SELF_CUSTODY depending on your company.
Self-custody only: before this wallet can send funds, the customer signs a one-time permit so Venly can move their tokens. Do this once after the account is set up — see Approving transfers without gas.

Step 6 — Issue a virtual bank account (IBAN)

Once the account is VERIFIED, attach an IBAN so it can be funded via SEPA. Incoming euros are auto-converted to the targetCryptocurrency (USDC, EURC, or USDT) and credited to the wallet.
curl -X POST https://api-staging.venlyfinance.com/v1/accounts/{accountId}/virtual-bank-accounts \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Jane Doe — EUR Funding",
    "inCurrency": "EUR",
    "targetCryptocurrency": "USDC",
    "idempotencyKey": "f47ac10b-58cc-4372-a567-0e02b2c3d479"
  }'
Response (201)
{
  "success": true,
  "result": {
    "id": "4d5e6f70-8192-4a3b-9c4d-5e6f7081920a",
    "accountId": "b2a1f0e9-8c7d-4e3a-9f21-0a1b2c3d4e5f",
    "bankAccountType": "EUR_SEPA",
    "status": "ACTIVE",
    "currency": "EUR",
    "targetCryptocurrency": "USDC",
    "iban": "DE89370400440532013000",
    "bic": "DEUTDEDB",
    "referenceCode": "VFY-7K2Q-931"
  }
}
Surface the referenceCode to your end user when they initiate a SEPA transfer — it’s what links the incoming wire to this account. Without it, settlement falls back to manual reconciliation.

Step 7 — Fund the account

Option A — SEPA transfer to the IBAN

The customer sends EUR from their bank to the IBAN from Step 6, including the referenceCode in the payment reference. On receipt, Venly converts EUR → the target crypto and credits the wallet.

Option B — Hosted fiat-to-crypto pay-in

Create a pay-in session and redirect the customer to the returned paymentUrl.
curl -X POST https://api-staging.venlyfinance.com/v1/accounts/{accountId}/fiat-to-crypto/payment-sessions \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "inAmount": "100.00",
    "inCurrency": "EUR",
    "outCryptocurrency": "USDC",
    "callbackUrl": "https://your-app.com/webhooks/pay-in",
    "successRedirectUrl": "https://your-app.com/onboarding/funded",
    "failureRedirectUrl": "https://your-app.com/onboarding/retry",
    "idempotencyKey": "c7e8d9f0-1a2b-3c4d-5e6f-7a8b9c0d1e2f"
  }'
The response carries a paymentUrl. When the customer completes payment, the wallet is credited and your callbackUrl is invoked.

Step 8 — Send a transfer

Transfers move funds between two Venly accounts — identify the receiver by receiverAccountId or by your own receiverExternalId. Both the sender and the receiver accounts must be VERIFIED; sending to an unverified receiver fails with 422 receiver-kyc-not-verified.

Crypto transfer

curl -X POST https://api-staging.venlyfinance.com/v1/accounts/{senderAccountId}/transfers/crypto \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "receiverExternalId": "acct-67890",
    "chain": "BASE",
    "asset": "USDC",
    "amount": 25,
    "description": "Invoice #1138 settlement",
    "idempotencyKey": "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
  }'
Response (201)
{
  "success": true,
  "result": {
    "id": "a1b2c3d4-e5f6-4789-9abc-def012345678",
    "senderAccountId": "b2a1f0e9-8c7d-4e3a-9f21-0a1b2c3d4e5f",
    "receiverAccountId": "c3b2a1f0-9d8c-4e3a-bf21-1a2b3c4d5e60",
    "chain": "BASE",
    "asset": "USDC",
    "amount": 25,
    "status": "COMPLETED",
    "transactionHash": "0xa1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2",
    "createdAt": "2026-01-15T09:30:00Z",
    "updatedAt": "2026-01-15T09:30:02Z"
  }
}

Fiat transfer

A fiat-denominated transfer settles in the underlying stablecoin and returns a fiatOrigin block with the original currency and exchange rate.
curl -X POST https://api-staging.venlyfinance.com/v1/accounts/{senderAccountId}/transfers/fiat \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "receiverExternalId": "acct-67890",
    "currency": "USD",
    "amount": 25,
    "description": "Q2 supplier payment",
    "idempotencyKey": "e9f0a1b2-3c4d-5e6f-7a8b-9c0d1e2f3a4b"
  }'
Self-custody senders also need a CONFIRMED permit and a granted allowance before a transfer succeeds — see Approving transfers without gas. Venly-managed accounts don’t.

Step 9 — Track the transfer

Crypto and fiat transfers usually complete synchronously, but you can always fetch the latest state:
curl https://api-staging.venlyfinance.com/v1/accounts/{accountId}/transfers/{transferId} \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN"
statusMeaning
PENDINGAccepted, settlement in progress
COMPLETEDFinal — funds delivered (transactionHash populated)
FAILEDFinal — see errorMessage
List all transfers for an account with GET /accounts/{accountId}/transfers plus pagination parameters. Retrying a create with the same idempotencyKey returns the original transfer — no double-spend.

Common pitfalls

The account is still VERIFICATION_PENDING. A Venly admin must verify it first — poll GET /accounts/{id} until kycStatus is VERIFIED. (The party being ACTIVE is not enough.)
The customer hasn’t signed the token permit yet, so Venly has no allowance to move funds. Run the permit flow once, then retry.
Self-custody accounts must include the customer’s wallet address on creation. Venly-managed accounts must not.
The receiverAccountId / receiverExternalId doesn’t resolve to an account in your company. Transfers are account-to-account — both sides must be Venly accounts you own.
Tokens last 5 minutes. Catch 401, refresh, and retry the original call. Don’t pre-authenticate every request — cache the token.

Next steps

Getting started

The condensed party → account → verify → IBAN flow.

Browse the API reference

Every endpoint, field, and error code.

Permits & allowances

Gasless token approvals for self-custody accounts.

Contact support

Stuck? Get in touch with your accountId and the failing request.