Bulk Payment Initiation#
These endpoints allow a TPP to initiate several payments from one payment account of a Holvi customer with a single request, approved with a single Strong Customer Authentication (SCA).
The bulk is accepted only if the available balance of the account covers its total amount. After that the customer must approve it on their phone; the payments are created and sent only once the customer approves. You can poll the bulk status to follow the process and, once processed, read the state of every payment.
The endpoint supports SEPA, SEPA Instant and SWIFT (international) payments, up to 100 per bulk, and they are executed immediately: bulk payments cannot be scheduled. Verification of Payee is not available per payment inside a bulk; to check the payee names before initiating the bulk, see the bulk Verification of Payee endpoint.
Note
All these endpoints require authentication, see the authentication documentation for the full list of headers required for these endpoints.
POST /api/v2/payment-initiation/bulk/#
Example request:
POST /api/v2/payment-initiation/bulk/ HTTP/1.1
Accept: application/json
Authorization: Bearer testJWTAccessToken
Idempotency-Key: 5b1f2f0e-2b6a-4c1d-9c3a-8d1e6f7a9b0c
{
"payment_account": "b06d5478-0954-451a-a1e0-f1eeb25336be",
"payments": [
{
"amount": "20.00",
"unstructured_reference": "Invoice 1001",
"counterparty": {
"name": "Jean Sibelius",
"account_identifier": "FI7979977991294922",
"account_identifier_type": "iban"
}
},
{
"amount": "15.50",
"unstructured_reference": "Invoice 1002",
"instant": true,
"counterparty": {
"name": "Aino Ackté",
"account_identifier": "DE89370400440532013000",
"account_identifier_type": "iban"
}
}
]
}
Example response:
HTTP/1.1 202 ACCEPTED
Vary: Accept
Content-Type: application/json
{
"bulk_id": "3f0c1a2e-7b4d-4c8e-9a1f-2d3e4f5a6b7c",
"status_url": "https://api.psd2.holvi.com/api/v2/payment-initiation/bulk/3f0c1a2e-7b4d-4c8e-9a1f-2d3e4f5a6b7c/status/"
}
Parameters:
<string>payment_account: Payment account UUID. All the payments of the bulk are debited from this account.<list>payments: The payments to make, from 1 to 100. Each payment accepts the same fields as a single payment initiation, exceptpayment_account(taken from the bulk),due_date(bulk payments cannot be scheduled) andexecute_verification_of_payee(not available in a bulk).<string>amount: Payment amount<string>currency: (optional) ISO 4217 currency code. Defaults to the currency of the givenpayment_account. See list of supported currencies here.<json>counterparty: Payment receiver, see payment initiation.<string>unstructured_reference: (optional) Max length of 140 characters. Exactly one ofstructured_referenceorunstructured_referencemust be provided; sending both is rejected.<string>structured_reference: (optional) Max length of 35 characters. Exactly one ofstructured_referenceorunstructured_referencemust be provided; sending both is rejected.<string>end_to_end_id: (optional) Max length of 36 characters<string>method: (optional) Allowed methods aresepaandinternational. Defaults tosepa.<boolean>instant: (optional) Set totrueto request a SEPA Instant payment. Defaults tofalse. It can only be used whenmethodis set tosepa.
Request Headers:
Host: Must beapi.psd2.holvi.comDate: Must follow the format described in section 7.1.1.1 of RFC 7231Digest: See authentication documentation for more detailsSignature: See authentication documentation for more detailsX-Holvi-Client-Id: Client ID provided by HolviX-Holvi-Client-Secret: Client secret provided by HolviAuthorization: JWT token to authenticateIdempotency-Key: Unique key chosen by the TPP for this bulk, max length of 255 characters. See idempotency below.
Status Codes:
202: The bulk has been accepted and the customer has been asked to approve it. Pollstatus_urlto follow it.400: The request is invalid, the available balance does not cover the total amount of the bulk, theIdempotency-Keyheader is missing, or the key was already used with a different request body. Errors of a single payment are reported under its position inpayments, see below.401: Unsuccessful authentication403: Forbidden. The user does not have permission to perform this action.Permissions granted to the user do not include payment initiation.
The user is not verified.
409: A request with the sameIdempotency-Keyis still being processed. Retry shortly.503: The payment service is unavailable. Retry with the sameIdempotency-Key.
Example error responses:
Errors detected while validating the request body are reported in a list with one entry per payment, empty for the payments that are valid:
HTTP/1.1 400 BAD REQUEST
Vary: Accept
Content-Type: application/json
{
"payments": [
{},
{
"counterparty": {
"account_identifier": ["Checksum does not validate"]
}
}
]
}
Errors detected while accepting the bulk are reported in an object keyed by the index of the payment, whose value is either a message or an object with the field concerned:
{
"payments": {
"1": "The IBAN number is not part of the SEPA area. Please contact support@holvi.com for more details."
}
}
Errors about the bulk as a whole are reported as a list of messages, under payments when they concern the
list itself and under non_field_errors otherwise:
{
"payments": ["Maximum 100 payments per bulk request."]
}
{
"non_field_errors": ["Amount can't be greater than the account balance!"]
}
Idempotency#
The Idempotency-Key header is required. It protects the customer from being asked to approve, and
pay, the same bulk twice when a request is retried, for example after a network timeout. Keys are
scoped to the TPP and the customer: a key used by another TPP or for another customer is unrelated.
Retrying with the same key and the same request body returns the bulk created by the first request, with the same
bulk_id. No second bulk is created and the customer is not asked again.Reusing a key with a different request body is rejected with a
400.While the first request is still being processed, a retry receives a
409.
HTTP/1.1 409 CONFLICT
Vary: Accept
Content-Type: application/json
{
"error": "request_in_progress",
"error_description": "A request with this Idempotency-Key is already being processed. Retry shortly."
}
Use a new key for every new bulk; a UUID is a good choice.
GET /api/v2/payment-initiation/bulk/{bulk_id}/status/#
Returns the current state of a bulk and, once it has been processed, the payments it contains,
on top of the bulk_id and status_url returned by the bulk creation.
Example request:
GET /api/v2/payment-initiation/bulk/3f0c1a2e-7b4d-4c8e-9a1f-2d3e4f5a6b7c/status/ HTTP/1.1
Accept: application/json
Authorization: Bearer testJWTAccessToken
Example response while the customer has not answered yet:
HTTP/1.1 200 OK
Vary: Accept
Content-Type: application/json
{
"bulk_id": "3f0c1a2e-7b4d-4c8e-9a1f-2d3e4f5a6b7c",
"status_url": "https://api.psd2.holvi.com/api/v2/payment-initiation/bulk/3f0c1a2e-7b4d-4c8e-9a1f-2d3e4f5a6b7c/status/",
"status": "pending_approval",
"processed": false,
"total_count": 2,
"total_amount": "35.50",
"not_sent_count": null,
"payments": []
}
Example response while the bulk is being processed:
{
"bulk_id": "3f0c1a2e-7b4d-4c8e-9a1f-2d3e4f5a6b7c",
"status_url": "https://api.psd2.holvi.com/api/v2/payment-initiation/bulk/3f0c1a2e-7b4d-4c8e-9a1f-2d3e4f5a6b7c/status/",
"status": "processing",
"processed": false,
"total_count": 2,
"total_amount": "35.50",
"not_sent_count": null,
"payments": []
}
Example response once the bulk has been approved and processed:
{
"bulk_id": "3f0c1a2e-7b4d-4c8e-9a1f-2d3e4f5a6b7c",
"status_url": "https://api.psd2.holvi.com/api/v2/payment-initiation/bulk/3f0c1a2e-7b4d-4c8e-9a1f-2d3e4f5a6b7c/status/",
"status": "approved",
"processed": true,
"total_count": 2,
"total_amount": "35.50",
"not_sent_count": 1,
"payments": [
{
"uuid": "9c6f2b0e-1d2a-4f3b-8c4d-5e6f7a8b9c0d",
"amount": "20.00",
"currency": "EUR",
"method": "sepa",
"booking_date": "2026-08-25",
"due_date": null,
"execution_at": "2026-08-25T10:15:03Z",
"state": "paid",
"direction": "out",
"is_credit": false,
"structured_reference": "",
"unstructured_reference": "Invoice 1001",
"end_to_end_id": "",
"counterparty": {
"name": "Jean Sibelius",
"bic": "",
"account_identifier": "FI7979977991294922",
"account_identifier_type": "iban",
"street": "",
"building": "",
"city": "",
"postcode": "",
"region": "",
"additional_info": "",
"country": ""
}
},
{
"uuid": "0d7a3c1f-2e3b-4a4c-9d5e-6f7a8b9c0d1e",
"amount": "15.50",
"currency": "EUR",
"method": "sepa",
"booking_date": null,
"due_date": null,
"execution_at": null,
"state": "notenoughbalance",
"direction": "out",
"is_credit": false,
"structured_reference": "",
"unstructured_reference": "Invoice 1002",
"end_to_end_id": "",
"counterparty": {
"name": "Aino Ackté",
"bic": "",
"account_identifier": "DE89370400440532013000",
"account_identifier_type": "iban",
"street": "",
"building": "",
"city": "",
"postcode": "",
"region": "",
"additional_info": "",
"country": ""
}
}
]
}
Example response when the customer declined, or did not answer within one hour:
{
"bulk_id": "3f0c1a2e-7b4d-4c8e-9a1f-2d3e4f5a6b7c",
"status_url": "https://api.psd2.holvi.com/api/v2/payment-initiation/bulk/3f0c1a2e-7b4d-4c8e-9a1f-2d3e4f5a6b7c/status/",
"status": "rejected",
"processed": true,
"total_count": 2,
"total_amount": "35.50",
"not_sent_count": 2,
"payments": []
}
Response body:
bulk_id: UUID of the bulk, use it to poll its status.status_url: URL of this endpoint for the bulk.status: One of the bulk payment states.processed:trueonce the bulk has reached its final state,approvedorrejected.total_count: Number of payments in the bulk.total_amount: Sum of the amounts of all the payments in the bulk, as given in the request and without currency conversion, so it is only meaningful when all the payments use the currency of the payment account, which they should.not_sent_count: Number of payments that were not sent,nulluntil the bulk is processed. If the balance drops between the approval and the sending, a payment it no longer covers is not sent and ends in thenotenoughbalancestate; the other payments of the bulk are still sent.payments: The payments of the bulk, in the same shape as a single payment initiation. Empty until the bulk is processed.
Request Headers:
Host: Must beapi.psd2.holvi.comDate: Must follow the format described in section 7.1.1.1 of RFC 7231Signature: See authentication documentation for more detailsX-Holvi-Client-Id: Client ID provided by HolviX-Holvi-Client-Secret: Client secret provided by HolviAuthorization: JWT token to authenticate
Status Codes:
200: No error401: Unsuccessful authentication403: Forbidden. Permissions granted to the user do not include payment initiation.404: The bulk does not exist for this TPP and customer.503: The payment service is unavailable.