Bovillus Finance
Contract v3.0.0 published 11 Nov 2026 Endpoints open at go-live Production https://api.bovillus.finance/v3 · Sandbox https://sandbox.api.bovillus.finance/v3

Bovillus Finance Developers

Payments infrastructure for receiving banks and institutions

Bovillus Finance is an executing settlement institution. This API lets receivers follow every payment we send them and confirm settlement, lets sending institutions advise inbound credits, and lets account holders request payouts and read balances and statements.

PaymentsInstructions we send to you, with every status change and the exact message delivered.
Receiver confirmationsYour final word: credited, rejected or returned. Only this makes a payment final.
Payment requestsAsk us to pay out from your own account. We approve before any money moves.
Inbound transfersAdvise a credit to a Bovillus account. Validated, matched, then posted once.
AccountsBooked and available balances, entries and camt.053 statements.
WebhooksSigned events pushed to you, retried for 72 hours.

Principles

  • Nothing is final until the receiver says so. Funds are reserved when we generate a payment and debited only on your ACSC confirmation.
  • Every write is signed and idempotent. A repeated Idempotency-Key returns the first result; it never makes a second payment.
  • ISO 20022 throughout. Field names, status codes, reason codes, purpose codes and camt.053 statements follow the standard, so your systems can map us without translation.
  • Money is a string. "25000.00", never a floating-point number.
  • Formats are files, not networks. We deliver pacs.008, MT103 or JSON as you choose. Bovillus Finance is not a SWIFT member; MT messages are FIN-format files.

Delivery modes

We deliver each payment by the mode agreed at onboarding: API, SFTP, IP_TO_IP (API or SFTP inside a private tunnel), T2T, KTT or MANUAL. Whatever the mode, you confirm through this API, or Bovillus Finance records your confirmation with its evidence.

Onboarding

Access is granted per institution. Documentation is public; credentials are not.

  1. Agreement and profile. Sign the receiver document. We agree your delivery mode, message format, currencies, limits and the accounts your profile may use.
  2. Key pair and certificate request. Generate an EC P-256 key pair in your HSM or key store and send us a certificate signing request. Never send a private key.
    openssl ecparam -name prime256v1 -genkey -noout -out client.key
    openssl req -new -key client.key -out client.csr \
      -subj "/O=Your Institution Ltd/CN=payments-api/C=HK"
  3. We issue your client certificate, client_id and partner code, through the agreed secure channel.
  4. Register your signing key. Give us your public key or a JWKS URL and the keyid you will use in signatures.
  5. Sandbox tests. Pass the go-live checklist below in the sandbox.
  6. Production. After written go-ahead from both operations teams, we enable your production profile and IP allow-list.

Go-live checklist

#TestPass when
1Token with valid and invalid assertion200 with token; 401 INVALID_TOKEN
2Signed write, then the same with a broken signature2xx; then 401 INVALID_SIGNATURE
3Same Idempotency-Key twice, then with a changed bodySame result twice; then 409 IDEMPOTENCY_CONFLICT
4Scenario REJECT_AC01Status RJCT, reason AC01
5Receiver confirmation ACSCPayment ACSC, fee chargedAt set
6Webhook received and its signature verifiedYour endpoint replies 2xx; duplicate eventId ignored

Quickstart

Get a token, then confirm a payment. Every call uses your client certificate.

1. Get an access token

curl https://sandbox.api.bovillus.finance/v3/oauth2/token \
  --cert client.crt --key client.key \
  -d grant_type=client_credentials \
  -d client_assertion_type=urn:ietf:params:oauth:client-assertion-type:jwt-bearer \
  -d client_assertion="$ASSERTION" \
  -d scope="payments:read confirmations:write"

# 200
{ "access_token": "eyJ…", "token_type": "Bearer", "expires_in": 300,
  "scope": "payments:read confirmations:write" }

2. Sign the request

Every write carries Content-Digest and an RFC 9421 signature over the method, URL, digest, idempotency key and request id. The signature base is exactly these lines:

"@method": POST
"@target-uri": https://sandbox.api.bovillus.finance/v3/payments/2b8f4c1a-…/receiver-confirmations
"content-digest": sha-256=:9Xq2…=:
"idempotency-key": skit-conf-0001-acsc
"x-request-id": 6f1c2d1e-8a1b-4b7e-9f0e-2b6f5c1d9a10
"@signature-params": ("@method" "@target-uri" "content-digest" "idempotency-key" "x-request-id");created=1794400000;keyid="partner-skit-2026-11";alg="ecdsa-p256-sha256"
import { createHash, createPrivateKey, sign } from "node:crypto";

export function signedHeaders({ method, url, body, idemKey, requestId, keyId, pem }) {
  const digest = "sha-256=:" + createHash("sha256").update(body).digest("base64") + ":";
  const created = Math.floor(Date.now() / 1000);
  const params = `("@method" "@target-uri" "content-digest" "idempotency-key" "x-request-id")` +
                 `;created=${created};keyid="${keyId}";alg="ecdsa-p256-sha256"`;
  const base = [
    `"@method": ${method}`, `"@target-uri": ${url}`, `"content-digest": ${digest}`,
    `"idempotency-key": ${idemKey}`, `"x-request-id": ${requestId}`,
    `"@signature-params": ${params}`].join("\n");
  const sig = sign("sha256", Buffer.from(base),
                   { key: createPrivateKey(pem), dsaEncoding: "ieee-p1363" }).toString("base64");
  return { "Content-Digest": digest, "Idempotency-Key": idemKey, "X-Request-ID": requestId,
           "Signature-Input": `sig1=${params}`, "Signature": `sig1=:${sig}:` };
}

3. Confirm the payment

POST /v3/payments/2b8f4c1a-6d3e-4f7a-9c2b-1e5d8a7f3c90/receiver-confirmations
Authorization: Bearer eyJ…
Content-Type: application/json
…signed headers from step 2…

{
  "result": "ACSC",
  "receiverReference": "SKIT-CR-889102",
  "settledAmount": { "currency": "USD", "value": "1000.00" },
  "valueDate": "2026-11-12",
  "confirmedAt": "2026-11-12T08:15:00Z"
}

# 201 → payment.status "ACSC", bovillusState "SETTLED_BY_RECEIVER", fee.chargedAt set

Security profile

The API follows the OpenID Foundation FAPI 2.0 Security Profile, the profile open-banking regimes use for payment APIs.

LayerControlStandard
TransportTLS 1.2+ with your client certificate on every callMutual TLS
Client authenticationA JWT signed with your private key; we hold only your public keyOAuth 2.0 private_key_jwt, RFC 7523
Access tokens300 seconds, scoped, bound to your certificateRFC 8705
Message integrityEvery write signed; body hashed; 300-second freshness windowRFC 9421, RFC 9530
DuplicatesIdempotency-Key on every write, kept 30 daysIETF Idempotency-Key header
Our signaturesWebhooks and responses signed by Bovillus Finance; keys at /.well-known/jwks.jsonRFC 9421, RFC 7517
NetworkPer-partner IP allow-list, rate limits, web application firewallIETF RateLimit fields
Keys at BovillusSigning keys in AWS KMS; credentials in AWS Secrets ManagerFIPS 140-validated modules
AuditEvery request recorded with partner, certificate, address, time and result, append-only-

Scopes

ScopeAllows
A token is useless without the certificate it was issued to, and a signed request cannot be replayed after 300 seconds or changed in transit. Lose a key: tell us and we revoke the certificate at once.

Payment lifecycle

Statuses are ISO 20022 transaction status codes. bovillusState says the same thing in our words.

RCVD
Received
PDNG
Awaiting approval (payment requests)
ACTC
Generated · funds reserved
ACSP
Delivered · acknowledged
ACSC
Settled by receiver · debit and fee posted
RJCT rejected · reservation releasedRTND returned after acceptanceCANC cancelled before delivery
statusbovillusStateWhat has happened to the money
RCVDRECEIVEDNothing yet
PDNGAWAITING_APPROVALNothing yet; Bovillus Finance is reviewing the request
ACTCGENERATEDAmount and fee reserved; available balance reduced
ACSPDELIVERED / ACKNOWLEDGEDStill reserved; the receiver has the instruction
ACSCSETTLED_BY_RECEIVERDebited, success fee charged, reservation closed. Final.
RJCT / RTND / CANCREJECTED / RETURNED / CANCELLEDReservation released; nothing debited

Reason codes

Rejections and returns carry an ISO 20022 ExternalStatusReason1Code, for example AC01 incorrect account, AC04 closed account, AM04 insufficient funds, RR04 regulatory reason.

For receiving banks and institutions

Receiving payments from Bovillus Finance

How a payment reaches you, what you return, and how you confirm settlement. You choose the delivery mode at onboarding; the API mode is described in full here.

1
Bovillus approves the payment and reserves the funds (ACTC)
2
Signed instruction to your endpoint, from 18.184.101.36
3
You answer 202 with your reference (ACSP)
4
You confirm credit: ACSC. Bovillus posts the debit and fee

The endpoint you provide

One HTTPS endpoint, for example POST https://api.yourbank.example/bovillus/payments, registered at onboarding with a test and a production URL. Allow-list our address 18.184.101.36.

ItemWhat Bovillus Finance sends
BodyJSON instruction (application/json), or ISO 20022 pacs.008.001.08 (application/xml) validated against the XSD before sending. MT103 copies can be attached.
AuthenticationYour choice: mutual TLS with the Bovillus client certificate, an OAuth 2.0 token we obtain from your token endpoint, or an API key header you name.
Idempotency-KeyThe instructionId. A repeat must return your first answer, never a second credit.
X-Request-IDUnique per attempt, for your logs and ours.
SignatureRFC 9421 Signature-Input and Signature with Content-Digest, made with our key. Verify with https://api.bovillus.finance/v3/.well-known/jwks.json.
POST /bovillus/payments HTTP/1.1
Host: api.yourbank.example
Content-Type: application/json
Idempotency-Key: BOV-20261111-000123
X-Request-ID: 0d6e2c4a-9b1f-4f3e-8a7c-5e2d1b9f6a40
Content-Digest: sha-256=:…=:
Signature-Input: sig1=("@method" "@target-uri" "content-digest" "idempotency-key" "x-request-id");created=1794400000;keyid="bovillus-2026-11";alg="ecdsa-p256-sha256"
Signature: sig1=:…:

{
  "instructionId": "BOV-20261111-000123",
  "endToEndId": "SKIT-RED-0001",
  "uetr": "8a4d2f6e-3b1c-4e9a-8f7d-2c5b1a9e6d30",
  "paymentId": "2b8f4c1a-6d3e-4f7a-9c2b-1e5d8a7f3c90",
  "amount": { "currency": "USD", "value": "1000.00" },
  "debtor": { "name": "Bovillus Finance" },
  "debtorAccount": { "accountNumber": "08122800051" },
  "creditor": { "name": "SK Information Technologies" },
  "creditorAccount": { "otherId": "SKIT-CIS-20260827" },
  "purposeCode": "SUPP",
  "remittanceInformation": "Redemption lot 1",
  "confirmationUrl": "https://api.bovillus.finance/v3/payments/2b8f4c1a-…/receiver-confirmations",
  "createdAt": "2026-11-11T09:30:00Z"
}

What you return

Your answerMeaningWhat Bovillus does
202 with receiverReference, status ACSPAccepted for processingMarks DELIVERED / ACKNOWLEDGED; funds stay reserved
200 same body as beforeA repeat you already acceptedNo change
202 status ACSCAlready creditedTreated as your confirmation; debit and fee posted
4xx with ISO reason codeRefused (final)Marks RJCT; reservation released
5xx, 429 or no answer in 30 sTemporaryRetries with the same key, with back-off, for up to 24 hours
HTTP/1.1 202 Accepted
Content-Type: application/json

{ "receiverReference": "SKIT-IN-55120", "status": "ACSP" }

Confirming settlement

A 2xx means accepted, not credited. When the beneficiary is credited, rejected or the funds are returned, tell us in one of three ways:

  1. Confirmation API (recommended). POST /v3/payments/{paymentId}/receiver-confirmations with ACSC, RJCT or RTND. See the Quickstart.
  2. Status endpoint. Give us GET on your side by instructionId; we poll it until final.
  3. Officer confirmation. For KTT, T2T and manual delivery, your officer confirms in writing with your reference; Bovillus Finance records it with the evidence.

Other delivery modes

ModeHow it reaches you
SFTPJSON or pacs.008 file with a SHA-256 manifest uploaded to your path, key-based authentication; you return an acknowledgement file
IP_TO_IPThe API or SFTP mode inside a private tunnel between fixed addresses
T2TRaw TCP session to your listener; no web proxy or CDN in front of it
KTTTested telex from 210616 BOVILS G with the agreed test key
MANUALVerification session link and access code with the institutional PDF and package files

Webhooks

Subscribe once; we push each status change to your HTTPS endpoint.

  • Events: payment.status_changed, payment_request.status_changed, inbound_transfer.status_changed.
  • Each delivery is signed with RFC 9421 by Bovillus Finance, with Content-Digest. Verify against the key named by keyid at /.well-known/jwks.json, and refuse anything older than 300 seconds.
  • Reply 2xx within 10 seconds, then process. We retry for 72 hours with exponential back-off.
  • Delivery is at least once: keep each eventId and ignore repeats.
  • A webhook tells you something changed; GET /payments/{paymentId} is the record.
{
  "eventId": "c1f7a0e2-5b3d-4c8e-9a6f-0d2b7e4c1a95",
  "type": "payment.settled",
  "occurredAt": "2026-11-12T08:15:03Z",
  "data": { "paymentId": "2b8f4c1a-…", "status": "ACSC", "bovillusState": "SETTLED_BY_RECEIVER", … }
}

Sandbox

The same contract at https://sandbox.api.bovillus.finance/v3, with no balance movement and no external delivery.

Set the Bovillus-Sandbox-Scenario header on a payment request or inbound transfer to force an outcome.

ScenarioResult
SUCCESSMoves through to ACSC with webhooks at each step
REJECT_AC01RJCT, reason AC01 incorrect account
REJECT_AM04RJCT, reason AM04 insufficient funds
RETURN_AC04ACSC, then RTND with reason AC04
DELAY_ACKStays ACSP for 15 minutes before acknowledgement
TIMEOUT503 SERVICE_UNAVAILABLE; retry with the same key succeeds
DUPLICATEReturns the stored first result, as a real repeat would

Errors

Every error is an RFC 9457 problem document with a stable code, your requestId, and whether a retry is safe.

HTTP/1.1 409 Conflict
Content-Type: application/problem+json

{ "type": "https://bovillus.finance/developers/errors/IDEMPOTENCY_CONFLICT",
  "title": "The Idempotency-Key was used with a different body",
  "status": 409, "code": "IDEMPOTENCY_CONFLICT",
  "requestId": "6f1c2d1e-8a1b-4b7e-9f0e-2b6f5c1d9a10", "retryable": false }
codeHTTPRetryWhat to do

Retry only when retryable is true, with the same Idempotency-Key. A 5xx never changes anything.

Changelog

3.0.0 · 11 November 2026

  • New interface under /v3: FAPI 2.0 security profile, signed writes, ISO 20022 statuses and fields.
  • Receiver confirmations replace the partner-specific settlement callback.
  • Partner-initiated payouts are now payment requests, approved by Bovillus Finance before funds move.
  • Accounts, camt.053 statements and webhooks added.

2.2 · deprecated

  • POST /v1/transfers is retired and returns 410.
  • /api/incoming/transfer, /api/settlement/callback, /api/v2/* and /api/canonical/* stay available to existing partners until each has moved to v3, and close 180 days after that partner's notice.

Download the contract

OpenAPI 3.1, the single source for this portal. Copy it, or ask Bovillus Finance for the file.