API Reference

The main endpoints, grouped by module. For every endpoint with its full request and response schemas, see the generated OpenAPI spec at https://api.finveil.money/v3/api-docs, or browse it in the live API reference.

Base URL & authentication

Base URL

https://api.finveil.money

There is no separate sandbox host. Test mode runs on the production API, and the key or account mode decides whether a request is TEST or LIVE.

Authentication

Server-to-server calls use an API key, sent as X-API-Key: fvk_test_… or Authorization: Bearer fvk_test_…. Keys are fvk_test_… or fvk_live_…. Mint them in the dashboard or with POST /api/api-keys. Admin actions, such as minting keys or activating Connect accounts, need a signed-in ADMIN session (JWT).

The free Sandbox plan covers payment links, Connect splits, payment status and API keys. Paid-plan routes, including /v1/**, return 403 FV-1003 on the Sandbox plan.

Authentication & API keys

Register, sign in for a JWT session (1-hour access token, 7-day refresh token), and mint API keys. Only an ADMIN session can mint, rotate or revoke keys.

POST/api/auth/register
POST/api/auth/login
POST/api/auth/refresh
POST/api/api-keys
POST/api/api-keys/{id}/rotate
POST/api/api-keys/{id}/revoke
GET/api/account/status

Connect: split payments

Connected accounts become Paystack subaccounts, and a split becomes a Paystack split code. Paystack settles each party’s share to its subaccount. Activating accounts and splits needs an ADMIN session.

POST/api/connect/accounts
POST/api/connect/accounts/{id}/activate
POST/api/connect/splits
POST/api/connect/splits/{id}/activate
GET/api/connect/splits/{id}

Payment status & history

Available on the free Sandbox plan. Another business’s id or reference returns 404.

GET/api/payments/history
GET/api/payments/{transactionId}
GET/api/payments/by-reference/{reference}/status

Direct collections & refunds (paid plan)

Collect against a provider you name. Paystack is verified end to end in test mode. Yoco and PayFast run against sandbox credentials. Ozow is not connected.

POST/api/payments/collect
POST/api/payments/refund

Orchestration API /v1 (paid plan)

The routing engine scores eligible providers and rails, picks one, and records the decision and its fallback order. Failover along that order runs in test mode only.

POST/v1/payments
POST/v1/payouts
POST/v1/refunds
GET/v1/transactions/{id}
POST/v1/recipients

Webhook endpoints (paid plan)

Register an endpoint for /v1 events. See the webhooks page for event types and signature verification.

POST/v1/webhook_endpoints
GET/v1/webhook_endpoints
DELETE/v1/webhook_endpoints/{id}
POST/v1/webhook_endpoints/deliveries/{id}/replay

Proof receipts

Settled transactions get a SHA-256 proof receipt. Receipts are batched hourly into a Merkle root that FinVeil stores in its own database. Verification goes through FinVeil’s API. Public-ledger anchoring is not live.

GET/api/tokens/{tokenId}/verify
GET/api/verify/receipt/{receiptId}

Rates & tax

SARB rates are a daily snapshot with source and observation date. They are not real-time.

GET/api/rates/sarb/latest
GET/api/rates/cpi/headline
GET/api/rates/fx
GET/api/tax/public/compare
POST/api/tax/estimate

Errors

Errors come back as JSON with a single error object containing:

  • code: a stable FinVeil error code, such as FV-1003.
  • message: a human-readable summary.
  • traceId: quote it when you contact support.
  • timestamp: when the error was produced, in ISO-8601.
  • retryable: whether retrying makes sense.
  • retryAfterSeconds: a suggested wait when the error is retryable, otherwise null.
  • details: extra context for this error.

A 429 means you hit a rate limit: wait for the Retry-After seconds. Send an Idempotency-Key header on any POST you might retry. Reusing a key with a different body returns 409.

FV-3003.httphttp
HTTP/1.1 400 Bad Request
Content-Type: application/json

{
  "error": {
    "code":      "FV-3003",
    "message":   "Invalid amount",
    "traceId":   "trace_42cb62e80e94440b",
    "timestamp": "2026-09-14T09:12:44.091Z",
    "retryable": false,
    "retryAfterSeconds": null,
    "details":   {}
  }
}