Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.venlyfinance.com/llms.txt

Use this file to discover all available pages before exploring further.

This guide walks you end-to-end through the Finance API — from creating your first party to settling a transfer. Every step uses real request and response shapes from the live spec. Run it against staging and you’ll have a working integration in under an hour.
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

By the end of this walkthrough you’ll have:
  1. Onboarded an individual party (your customer)
  2. Opened an account for them on the Base blockchain
  3. Provisioned a custodial wallet to hold their crypto
  4. Issued a EUR IBAN (virtual bank account) so they can fund via SEPA
  5. Sent an outbound transfer to settle funds
Here’s the full flow at a glance:

Prerequisites

Before you begin, make sure you have:
1

Sandbox credentials

A client_id and client_secret from your Venly account manager. Sandbox credentials never touch real money.
2

An HTTP client

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

A UUID generator

Several endpoints require an idempotencyKey (UUID v4). Use any generator — uuidgen on macOS/Linux, [guid]::NewGuid() in PowerShell, or crypto.randomUUID() in JavaScript.

Step 1 — Authenticate

Exchange your client credentials for a Bearer token. Every subsequent call needs this token in the Authorization header.
curl -X POST https://login-sandbox.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 proactively 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 legal identity behind every account — either an INDIVIDUAL (natural person) or an ORGANISATION (company). KYC/KYB is initiated automatically when you create the 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",
    "firstName": "Jane",
    "lastName": "Doe",
    "address": {
      "addressLine1": "123 Main Street",
      "city": "Amsterdam",
      "postalCode": "1012AB",
      "country": "NL"
    }
  }'
Response (201)
{
  "success": true,
  "result": {
    "id": "9b1deb4d-3b7d-4bad-9bdd-2b0d7b3dcb6d",
    "partyType": "INDIVIDUAL",
    "firstName": "Jane",
    "lastName": "Doe",
    "status": "ACTIVE",
    "kycStatus": "PENDING"
  }
}
The kycStatus starts as PENDING. KYC checks run asynchronously; the party can be referenced immediately, but transfers will be blocked until kycStatus = VERIFIED. Poll GET /parties/{partyId} until it transitions, or watch for it during account creation.
For organisations, swap the body:
{
  "partyType": "ORGANISATION",
  "name": "Acme B.V.",
  "vatNumber": "NL123456789B01",
  "address": { "addressLine1": "...", "city": "...", "postalCode": "...", "country": "NL" }
}

Step 3 — Open an account

An account is the container that holds wallets, IBANs, and transfer history. It must reference a blockchain network — currently BASE or AVALANCHE. You can either reference the party you just created (partyId), or create the party inline (party object). The two-call flow shown here is more common in production because it lets KYC progress while you build the rest of the account.
curl -X POST https://api-staging.venlyfinance.com/v1/accounts \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "externalId": "user-jane-doe-001",
    "name": "Jane Doe — Main",
    "chain": "BASE",
    "partyId": "9b1deb4d-3b7d-4bad-9bdd-2b0d7b3dcb6d"
  }'
Response (201)
{
  "success": true,
  "result": {
    "id": "62d0b8e8-7c0a-4e4c-9c7f-1d2a3b4c5d6e",
    "externalId": "user-jane-doe-001",
    "status": "ACTIVE",
    "partyRoles": [
      {
        "partyId": "9b1deb4d-3b7d-4bad-9bdd-2b0d7b3dcb6d",
        "roleType": "ACCOUNT_HOLDER",
        "status": "ACTIVE"
      }
    ]
  }
}
Always set a meaningful externalId — your own user/customer ID. It becomes the receiver shortcut in transfers (receiverExternalId) so you don’t have to look up the Venly UUID every time.

Joint accounts (optional)

To add a second account holder, call the party-roles endpoint:
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 — Provision a wallet

Every account needs a custodial wallet to hold its crypto balance. The chain you specify must match the account’s chain.
curl -X POST https://api-staging.venlyfinance.com/v1/accounts/{accountId}/wallets \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "chain": "BASE" }'
Response (201)
{
  "success": true,
  "result": {
    "id": "8a2f4c7e-5b1d-4e3a-9c8f-2a3b4c5d6e7f",
    "accountId": "62d0b8e8-7c0a-4e4c-9c7f-1d2a3b4c5d6e",
    "chain": "BASE",
    "address": "0x742d35Cc6634C0532925a3b844Bc9e7595f8b2C9",
    "status": "ACTIVE",
    "balances": []
  }
}
The returned address is a real blockchain address on Base. Funds credited to this wallet are custodied — Venly holds the keys, and your app moves balances via the API.

Step 5 — Issue a virtual bank account (IBAN)

To accept fiat funding, attach a virtual bank account to the account. In Release 1, only EUR_SEPA is supported — incoming euros are auto-converted to the cryptocurrency you specify (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": "7b3c1a4e-9f2d-4e8a-bc1d-0e5f6a7b8c9d",
    "accountId": "62d0b8e8-7c0a-4e4c-9c7f-1d2a3b4c5d6e",
    "bankAccountType": "EUR_SEPA",
    "status": "ACTIVE",
    "currency": "EUR",
    "targetCryptocurrency": "USDC",
    "iban": "NL91ABNA0417164300",
    "bic": "ABNANL2A",
    "beneficiaryName": "Venly Custody B.V.",
    "referenceCode": "VEN-JD0001-7B3C"
  }
}
Surface the referenceCode to your end user when they initiate a SEPA transfer. The reference is what links the incoming wire to this specific account — without it, settlement falls back to manual reconciliation.

Step 6 — Fund the account

You now have two ways to credit the account:

Option A — Customer-initiated SEPA transfer

The customer sends EUR from their personal bank to the IBAN you issued in Step 5, including the referenceCode in the payment message. Settlement is typically same-day for SEPA Instant, T+1 for standard SEPA. The wallet balance updates automatically on credit.

Option B — Hosted fiat-to-crypto checkout

For card-funded onboarding, create a payment session. Venly returns a hosted checkout URL you redirect the customer to.
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/payment-session",
    "successRedirectUrl": "https://your-app.com/onboarding/funded",
    "failureRedirectUrl": "https://your-app.com/onboarding/retry",
    "idempotencyKey": "c7e8d9f0-1a2b-3c4d-5e6f-7a8b9c0d1e2f"
  }'
The response contains a paymentUrl — open it in the customer’s browser. Once they complete payment, the wallet is credited and your callbackUrl is invoked.

Step 7 — Send a transfer

With the wallet funded, you can now move money out. Three transfer types are supported:

Account-to-Account

Internal — between two Venly accounts. Settles in seconds, no on-chain fee.

Crypto Transfer

External — on-chain to any wallet address. Subject to network fees.

Fiat Transfer

External — SEPA payout to a bank account.

Internal (Account-to-Account)

Fastest path — moves crypto between two Venly-custodied accounts without touching the blockchain.
curl -X POST https://api-staging.venlyfinance.com/v1/account-to-account-transfers \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "sourceAccountId": "62d0b8e8-7c0a-4e4c-9c7f-1d2a3b4c5d6e",
    "destinationAccountId": "ANOTHER_ACCOUNT_UUID",
    "amount": "50.000000",
    "cryptocurrency": "USDC"
  }'

External crypto transfer

Sends USDC on Base to any blockchain address.
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 '{
    "chain": "BASE",
    "asset": "USDC",
    "amount": 25.5,
    "receiverExternalId": "user-acme-merchant-042",
    "description": "Invoice #1138 settlement",
    "idempotencyKey": "d8e9f0a1-2b3c-4d5e-6f7a-8b9c0d1e2f3a"
  }'
For a transfer to an external (non-Venly) wallet, omit receiverExternalId/receiverAccountId and provide the destination address — see the Create Crypto Transfer reference.

External fiat transfer (SEPA out)

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 '{
    "currency": "EUR",
    "amount": 250.00,
    "receiverExternalId": "vendor-acme-001",
    "description": "Q2 supplier payment",
    "idempotencyKey": "e9f0a1b2-3c4d-5e6f-7a8b-9c0d1e2f3a4b"
  }'

Step 8 — Track the transfer

Every transfer endpoint returns a transfer object with status. Poll for status changes:
curl https://api-staging.venlyfinance.com/v1/accounts/{accountId}/transfers/{transferId} \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN"
Statuses follow this lifecycle:
StatusMeaning
PENDINGAccepted, not yet broadcast
PROCESSINGSubmitted to chain or settlement rail
COMPLETEDFinal — funds delivered
FAILEDFinal — see failureCode and failureReason
To list all transfers for an account, use GET /accounts/{accountId}/transfers with pagination parameters.
Webhooks for transfer status changes are not yet available — poll for now. See the roadmap for status updates.

Common pitfalls

The party’s kycStatus is still PENDING. Wait for VERIFIED before initiating outbound transfers. Inbound funding (Step 6) works regardless of KYC state but funds will be held until verification completes.
The body changed between calls. Idempotency keys are bound to the exact request body — see Idempotency for the full behaviour matrix. Generate a new UUID for any new request; reuse the same one only when retrying the identical call.
Check that the customer included the referenceCode in the payment message. Without it, the wire goes to manual reconciliation (24–48h delay).
Tokens are valid for 5 minutes. Catch 401 Unauthorized, refresh, and retry the original call. Don’t pre-authenticate every request — cache the token.

Next steps

Browse the API reference

Every endpoint, every field, every error code.

Authentication deep-dive

Token refresh patterns, environment URLs, credential rotation.

Permits & allowances

EIP-2612 token approvals for non-custodial flows.

Contact support

Stuck? Email us with your accountId and the failing request.