Skip to main content

Payment batches

A payment batch (payment_batch) groups supplier credit transfers debited from one bank account and approved together. Each transfer in the batch is a payment instruction (payment_instruction): a beneficiary, an amount, an execution date.

This API prepares batches - creating a draft, editing it, submitting it for approval, approving or refusing it -, hands an approved hosted_consent batch over to its channel, generates the SEPA file of an approved sepa_file batch, reads batches and their instructions, and lets you reconcile each instruction with the bank operation that settled it. None of these operations proves that a transfer was executed: only the batch's state and its instructions' status say anything about it.

What this surface publishes​

Twenty operations:

Verb and pathWhat it does
GET /api/v1/workspaces/{workspace_id}/companies/{company_id}/payment_batchesLists the company's batches
GET /api/v1/payment_batches/{id}Reads a batch
GET /api/v1/payment_batches/{id}/instructionsLists a batch's instructions
GET /api/v1/payment_instructions/{id}Reads an instruction
GET /api/v1/payment_batches/{id}/settlementReads the settlement of a batch's instructions and its suggestions
GET /api/v1/payment_batches/{id}/settlement/candidatesSearches the bank operations that may have settled an instruction, over the dates you choose
POST /api/v1/workspaces/{workspace_id}/companies/{company_id}/payment_batchesCreates a draft batch
PATCH /api/v1/payment_batches/{id}Edits a draft
POST /api/v1/payment_batches/{id}/submit_for_approvalSubmits a draft for approval
POST /api/v1/payment_batches/{id}/approveApproves a batch
POST /api/v1/payment_batches/{id}/refuseRefuses a batch's approval
POST /api/v1/payment_batches/{id}/submitHands an approved hosted_consent batch over to its channel
POST /api/v1/payment_batches/{id}/consent_sessionPublishes the consent page address of a hosted_consent batch that was handed over
POST /api/v1/payment_batches/{id}/settlement/suggestions/{suggestion_id}/dismissDismisses a suggestion
POST /api/v1/payment_batches/{id}/settlement/suggestions/{suggestion_id}/confirmConfirms a suggestion
POST /api/v1/payment_batches/{id}/settlement/confirmConfirms the bank operation that settled an instruction
POST /api/v1/payment_batches/{id}/settlement/instructions/{payment_instruction_id}/undoUndoes an instruction's reconciliation
POST /api/v1/payment_batches/{id}/settlement/instructions/{payment_instruction_id}/not_executedDeclares not executed an instruction whose execution is to be confirmed
POST /api/v1/payment_batches/{id}/sepa_exportGenerates a batch's SEPA file
GET /api/v1/payment_batches/{id}/sepa_exportReads a batch's export, or downloads its file

A token carrying the read scope is required on the first six. All the others require the write scope: the seven writes on batches, the five POST operations on settlement and the two operations on sepa_export, their GET included.

Listing a company's batches​

curl "https://app.scribee.tech/api/v1/workspaces/YOUR_WORKSPACE_ID/companies/YOUR_COMPANY_ID/payment_batches?state=submitted&per_page=50" \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN"

Batches come back from the most recently created to the oldest, and on an equal date from the highest identifier to the lowest. Pagination is by offset, with page and per_page - 20 by default, 100 at most - and meta carries current_page, per_page, total_pages and total_count.

Five filters, all optional and combinable:

FilterValuesWhat it keeps
statea state valueThe batches in that state
channelhosted_consent, sepa_fileThe batches handed over through that channel
bank_account_idan integerThe batches debited from that account
execution_date_froma YYYY-MM-DD dateThe batches whose execution_date is on or after that date
execution_date_toa YYYY-MM-DD dateThe batches whose execution_date is on or before that date

A filter we cannot apply returns an empty collection, never an error: an unknown state or channel value, an identifier that is not an integer, a date in any format other than YYYY-MM-DD. An absent or blank parameter is not a filter. A batch with no execution_date is kept by neither date filter.

Reading a batch​

curl https://app.scribee.tech/api/v1/payment_batches/7301 \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN"
{
"data": {
"id": 7301,
"company_id": 34,
"bank_account_id": 812,
"name": "Fournisseurs 2026-08",
"channel": "hosted_consent",
"state": "submitted",
"version": 6,
"instructions_count": 2,
"total_amount": 1250.5,
"currency_code": "EUR",
"execution_date": "2026-08-29",
"approved_at": "2026-08-27T10:02:11+02:00",
"submitted_at": "2026-08-27T10:05:40+02:00",
"progress": null,
"error_code": null,
"error_message": null,
"retryable": false,
"started_at": "2026-08-27T10:05:40+02:00",
"finished_at": "2026-08-27T10:05:43+02:00",
"submission_status": "accepted",
"submission_refused_by": null,
"verification_required": false,
"approved_by": {
"actor_type": "api_client",
"name": "Compta Connect",
"decided_at": "2026-08-27T10:02:11+02:00"
},
"refused_by": null,
"created_at": "2026-08-26T16:40:02+02:00",
"updated_at": "2026-08-27T10:05:43+02:00"
}
}

channel says how the batch leaves Scribee: hosted_consent, a payment request approved by consent given on a page of the bank; sepa_file, a SEPA credit transfer file produced by Scribee.

version changes on every write to the batch, including when one of its instructions is changed. One exception: an instruction moving to pending, settled or rejected does not change version on its own; only the change of batch state that may follow from it does.

started_at carries the same value as submitted_at. progress is always null: nothing measures a handover's progress, and we do not publish a made-up percentage.

The states of a batch​

stateWhat it means
draftThe batch is being prepared. A batch whose approval was refused comes back here
pending_approvalThe batch is waiting for a decision from a person authorised to approve it
approvedThe batch is approved. Its handover has not started, or it has started and its outcome is not known yet
submittedThe batch has been handed over to its channel. The outcome of at least one of its instructions is not known
completedEvery instruction of the batch is settled
partially_completedSome instructions of the batch are settled and the others rejected: each instruction's status says which
rejectedEvery instruction of the batch is rejected. This state is final
failedThe batch's handover was refused, by the payment provider or by Scribee before anything was sent: submission_refused_by says which. This state is final

Two states are final: rejected and failed. completed and partially_completed have one exit only: undoing a settlement that only a confirmation carried, which returns the batch to submitted (Undoing a confirmed settlement).

submitted does not prove that the transfers were executed. It says that the channel took the batch: for hosted_consent, the channel accepted the payment request; for sepa_file, the file was produced. The batch moves to completed, partially_completed or rejected only once every one of its instructions has a final status: while any of them is submitted or pending, the batch stays submitted. An instruction becomes settled when the bank reports executing it, or when you confirm the bank operation that settled it. Scribee receives no answer from the bank about a SEPA file: a sepa_file batch leaves submitted only through the confirmation of its instructions' settlement, or through the not-executed declaration of those whose execution is to be confirmed.

A handover under way whose outcome is not known​

An approved batch whose submitted_at is set is not a batch waiting to be handed over. The handover is under way, and its outcome is not known yet: a bank consent that has not been given yet, a SEPA file still being generated, or a channel that did not answer in time. finished_at stays null until the channel gives a final answer.

While the batch stays in this situation, its instructions cannot be handed over a second time. Poll the batch until it leaves approved, or wait for the payment_batch.updated event that announces every state change: it moves to submitted, then to completed, partially_completed or rejected if the channel's answer already carries the outcome of every instruction, or to failed.

Where the handover stands: submission_status​

state stays approved while the handover under way has no outcome. submission_status says which of these situations the batch is in:

submission_statusWhat it means
noneNo handover has been started
awaiting_consentThe payment request was created; the account holder has not authorised it yet on their bank's page
in_progressThe channel is still producing the handover, for example a SEPA file being generated
unknownNo usable answer from the channel has arrived
acceptedThe channel took the batch
refusedThe handover was refused, by the provider or by Scribee before anything was sent: the batch is failed, and submission_refused_by says by whom

A sepa_file batch whose handover is under way reads in_progress while its file is being generated, and unknown otherwise, until an outcome is recorded.

None of awaiting_consent, in_progress or unknown means that the payment was cancelled or rejected. The batch stays approved, a payment may still happen, and Scribee refuses any new handover of this batch while that is the case. A closed consent page, a return without success or a long wait proves neither a cancellation nor a rejection: only an answer from the channel moves the batch out of approved.

A check to make: verification_required​

verification_required is true when a hosted_consent batch has its handover awaiting_consent or unknown and it was started 24 hours ago or more (submitted_at). It is always false on a sepa_file batch. It turns false again as soon as every instruction of the batch is settled and carries a bank_operation_id: the bank already shows the money leaving the account, so there is nothing left to check. That is a reading only: the batch's state does not change, nothing is released, and the provider is still asked for its final status. It is an indication, not a conclusion: the payment must be checked with the provider, and the batch was neither cancelled nor rejected. Scribee derives no change of state from it, releases nothing, and keeps refusing a new handover.

verification_required is computed on every read. It can turn true without any change of state, so without any payment_batch.updated event: read the batch to know it.

When a handover fails​

error_code is operation_failed as soon as the latest handover attempt carries a diagnosis, and null otherwise. error_message is then a sentence written by Scribee, never the raw text returned by the channel.

Who refused the handover: submission_refused_by​

A batch moves to failed only when its started handover was refused with certainty. submission_refused_by says by whom:

submission_refused_byWhat it means
providerThe payment provider returned a refusal for the batch
localScribee refused the batch before anything was sent: the provider never received it, and no bank outcome is recorded. For example an incomplete batch, or a SEPA file that fails its validation
nullThe batch was not refused, or it was refused before this information was recorded: the origin of the refusal is not known

error_message gives the reason for the refusal. The batch's refusal, on its own, records no bank outcome for its instructions: it neither confirms nor rules out a rejection or an execution at the bank. Only an instruction that the provider's answer itself reports as refused moves to rejected (The status of an instruction). A failed batch is never handed over a second time.

error_code can be set on an approved batch. It describes the latest attempt - a channel that did not answer, for example - and not the outcome of the batch. Only the failed state says that the handover failed. rejected says something else: the handover went through, and the bank refused every instruction.

retryable says whether a new attempt could plausibly end differently. It is false on a batch that was never handed over.

Being notified of a change of state​

Rather than polling batches in a loop, subscribe a webhook endpoint to payment_batch.updated (Webhooks). The event is emitted on every change of state of a batch, and at that moment only; it carries the new state in state and the state left in previous_state. The ten transitions that emit it, and the eight keys of its body, are described on the Webhooks page.

A change of an instruction's published status is announced separately, by payment_instruction.updated, which carries the status left in previous_status (Webhooks).

A batch's instructions​

curl https://app.scribee.tech/api/v1/payment_batches/7301/instructions \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN"

Instructions come back in ascending order of their identifier, paginated like batches. An instruction can also be read on its own, with GET /api/v1/payment_instructions/{id}:

{
"data": {
"id": 61004,
"payment_batch_id": 7301,
"company_id": 34,
"invoice_document_id": 88120,
"beneficiary_name": "Papeterie Martin SARL",
"beneficiary_iban_masked": "FR*********************0189",
"beneficiary_iban_last4": "0189",
"amount": 830.0,
"currency_code": "EUR",
"execution_date": "2026-08-29",
"reference": "FA-2026-0310",
"end_to_end_id": "SCB-6100-78010",
"status": "submitted",
"created_at": "2026-08-26T16:41:15+02:00",
"updated_at": "2026-08-26T16:41:15+02:00"
}
}

invoice_document_id names the invoice the instruction pays, when it pays one; it is null otherwise. end_to_end_id is the transfer's end-to-end identifier, unique, 35 characters at most.

The beneficiary IBAN is never published in full. beneficiary_iban_masked keeps its first two characters and its last four, and replaces every other one with *; beneficiary_iban_last4 carries those last four characters. A value shorter than eight characters is masked entirely, and its beneficiary_iban_last4 is null.

The status of an instruction​

statusWhat it means
draftThe instruction's batch has not started a handover
submittedThe instruction's batch has started a handover, and the bank has given no answer about this instruction
submission_failedThe batch's handover was refused, and no bank outcome is recorded for this instruction. The batch is failed and is not handed over again
pendingThe bank accepted the instruction and has not executed it yet. Some banks never confirm beyond this point: do not treat it as paid
settledThe bank reported executing the instruction, or a person confirmed the bank operation that settled it
rejectedThe bank refused the instruction, or a person declared that it was not executed. This status is final

draft, submitted and submission_failed say nothing about the instruction's outcome at the bank; pending, settled and rejected say something about it. Approving a batch does not change its instructions: they stay draft until the batch starts its handover. An instruction moves to submitted the moment its batch starts its handover. It stays there as long as the handover's outcome is not known - consent pending, SEPA file still being generated, channel answer lost -, as it does on a sepa_file batch whose settlement nobody confirmed. It moves to submission_failed when the batch moves to failed, whether the refusal came from the provider or from Scribee before anything was sent; an instruction that already carries a status from the bank keeps its own. One exception: when the provider's answer refusing the handover itself reports an instruction of the batch as refused, that instruction moves to rejected, not to submission_failed. The batch's other instructions move to submission_failed; a failed batch can therefore hold both statuses, which the status filter separates. The API does not publish the reason for that refusal. On a partially_completed batch, every instruction is settled or rejected.

submission_failed is not a bank outcome recorded for this instruction: it neither confirms nor rules out a rejection or an execution at the bank - see the batch's refusal reason (error_message). To know who refused the handover, read submission_refused_by on the batch (Who refused the handover).

settled is left in one case only: undoing a confirmed settlement, on an instruction the bank has not reported executing. The instruction then returns to pending.

Neither a batch's state nor an instruction's status records the payment of an invoice. On this surface, only the confirmation of a settlement records it.

Two filters, optional and combinable, on the list of a batch's instructions:

FilterValuesWhat it keeps
statusdraft, submitted, submission_failed, pending, settled, rejectedThe instructions that publish that status
invoice_document_idan integerThe instructions that pay that invoice

As on batches, a filter we cannot apply returns an empty collection.

A batch's settlement suggestions​

curl https://app.scribee.tech/api/v1/payment_batches/7301/settlement \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN"

This read returns one row per instruction of the batch, in ascending order of payment_instruction_id. It is not paginated and carries no meta key: the collection is bounded by the batch's instructions.

{
"data": [
{
"payment_instruction_id": 61004,
"state": "matched",
"requires_review": false,
"bank_operation_id": 30401,
"invoice_payment_id": 51120,
"execution_to_confirm_at": null,
"not_executed_declaration": null,
"provider_evidence_conflict_at": null,
"late_provider_events": [],
"candidate_bank_operation_ids": [],
"matched_on": ["amount"],
"suggestions": []
},
{
"payment_instruction_id": 61005,
"state": "ambiguous",
"requires_review": true,
"bank_operation_id": null,
"invoice_payment_id": null,
"execution_to_confirm_at": null,
"not_executed_declaration": null,
"provider_evidence_conflict_at": null,
"late_provider_events": [],
"candidate_bank_operation_ids": [30402],
"matched_on": ["amount"],
"suggestions": [
{
"id": 91204,
"payment_instruction_id": 61005,
"bank_operation_id": 30402,
"state": "proposed",
"amount_match": "exact",
"day_offset": 2,
"window_start": "2026-08-24",
"window_end": "2026-09-08",
"explanation": "Outgoing operation of the same amount and currency booked 2 days after the planned execution date (2026-08-29), within the review window 2026-08-24 to 2026-09-08."
}
]
},
{
"payment_instruction_id": 61006,
"state": "unmatched",
"requires_review": false,
"bank_operation_id": null,
"invoice_payment_id": null,
"execution_to_confirm_at": null,
"not_executed_declaration": null,
"provider_evidence_conflict_at": null,
"late_provider_events": [],
"candidate_bank_operation_ids": [],
"matched_on": [],
"suggestions": []
}
]
}

A suggestion is a bank operation that looks like an instruction's debit: an outgoing operation on the account the batch debits, in the same currency, for exactly the instruction's amount, and dated within the review window. An operation already reconciled against invoices when the suggestions are computed is not suggested.

A suggestion proves neither that the instruction settled nor that the invoice was paid. It is put forward for a person to review, and nothing more: it does not change the instruction's status or the batch's state, and records no invoice payment. Only a confirmation does (Confirming an instruction's settlement).

Reading a row​

KeyWhat it carries
stateunmatched when no operation is suggested; ambiguous when one or more are and a person must review them; matched once the operation that settled the instruction has been confirmed
requires_reviewtrue when state is ambiguous, false otherwise
bank_operation_idThe confirmed operation; null until state is matched
invoice_payment_idThe invoice payment the confirmation recorded or adopted; null until state is matched, and on an instruction that pays no invoice
execution_to_confirm_atAn ISO 8601 date and time when the instruction's confirmed settlement was undone without the bank having reported its execution: the instruction returned to pending, and its execution remains to be confirmed. null on every other instruction, and again as soon as the bank reports the instruction executed or rejected, a bank operation is confirmed for it, or it is declared not executed
not_executed_declarationThe instruction's not-executed declaration: reason, note and declared_at, an ISO 8601 date and time. null on every instruction that was not declared not executed, including an instruction the bank refused
provider_evidence_conflict_atAn ISO 8601 date and time when a bank report, received while the instruction was settled, contradicted it: the instant the first one was received. null otherwise. It does not change the row's state
late_provider_eventsThe bank reports received after the fact, kept without being applied, oldest received first: scheme_status_code, occurred_at and received_at. [] otherwise
candidate_bank_operation_idsThe identifiers of the suggested operations, in ascending order
matched_on["amount"] when at least one operation is suggested or confirmed, [] otherwise
suggestionsThe detail of each suggested operation, in the same order; empty on a matched row

A single suggested operation also gives ambiguous: the API never picks a candidate for you.

Each element of suggestions carries:

KeyWhat it carries
idThe suggestion's identifier, which dismissing or confirming it addresses
payment_instruction_idThe instruction concerned
bank_operation_idThe suggested operation, readable on Bank operations
stateAlways proposed: the suggestion awaits review
amount_matchAlways exact: the operation carries exactly the instruction's amount, in the same currency
day_offsetThe number of calendar days between the operation date and the window's reference date, negative when the operation is earlier
window_start, window_endThe first and last day of the review window searched
explanationA sentence in English saying why the operation was suggested

The review window​

The window runs from 5 days before to 10 days after a reference date, in calendar days, bounds included. The reference date is the instruction's execution_date, else the batch's, else the day of submitted_at. An operation dated outside the window is never suggested; the manual search finds it.

The window filters candidates; it does not prove settlement.

When suggestions are computed​

Scribee computes suggestions in the background, not at the moment you read them: when the bank reports a status for the batch's instructions, when new bank operations arrive for the company, through a bank synchronization or an imported statement, and after a settlement is undone. A suggestion is computed only for an instruction that is not rejected and not already reconciled, in a batch that is submitted, completed or partially_completed, or approved with submitted_at set.

Each computation withdraws the suggestions whose operation is no longer a candidate - an instruction that has become rejected, an operation reconciled against invoices in the meantime - and they disappear from the row. Only suggestions awaiting review are published.

As with reading a batch, a batch your grants do not cover answers 404, even when bank reconciliation is disabled in its workspace.

A bank report received after the fact​

Once settled, an instruction no longer changes status on a bank report. When the bank reports on that instruction nonetheless - its settlement may have been confirmed before the bank answered -, Scribee keeps the report without applying it:

  • the instruction's status, its settlement row's state, its bank_operation_id and its invoice_payment_id do not change;
  • no invoice payment is recorded or removed, and no invoice amount is reserved or released;
  • keeping the report changes neither the batch's state nor its version.

The settlement row publishes these reports in late_provider_events, oldest received first. A report repeating the status and the code the instruction already carries is not listed, and neither is an ACSC: it confirms the instruction's execution, and it is kept as such (Undoing a confirmed settlement). The same code is listed only once, even when the bank repeats it on every read.

KeyWhat it carries
scheme_status_codeThe ISO 20022 code the bank reported
occurred_atThe instant that status was observed, an ISO 8601 date and time; null when it is not known
received_atThe instant Scribee received the report, an ISO 8601 date and time

A rejection received after the settlement contradicts the instruction. When the bank reports RJCT on a settled instruction, the settlement row carries provider_evidence_conflict_at, the instant that first contradicting report was received. The field is written only once and never reset to null: a later contradicting report is added to late_provider_events without changing it. Any other code is kept without setting this field.

{
"payment_instruction_id": 61004,
"state": "matched",
"requires_review": false,
"bank_operation_id": 30401,
"invoice_payment_id": 51120,
"execution_to_confirm_at": null,
"not_executed_declaration": null,
"provider_evidence_conflict_at": "2026-09-29T10:00:00+02:00",
"late_provider_events": [
{
"scheme_status_code": "RJCT",
"occurred_at": "2026-09-29T10:00:00+02:00",
"received_at": "2026-09-29T10:00:00+02:00"
}
],
"candidate_bank_operation_ids": [],
"matched_on": ["amount"],
"suggestions": []
}

provider_evidence_conflict_at decides nothing. The instruction stays settled, and the invoice payment the confirmation recorded stays in place: the contradiction is for a person to review. If the confirmed settlement turns out to be wrong, undo it.

No webhook signals a bank report received after the fact. Keeping it does not change the batch's state, and payment_batch.updated is emitted only on a state change; no other webhook event signals it. To detect it, read the settlement row: GET /api/v1/payment_batches/{id}/settlement.

Acting on an instruction's settlement​

Five operations turn these suggestions into decisions: dismissing a suggestion, searching for an operation outside the window, confirming the operation that settled an instruction, undoing that confirmation, and declaring not executed an instruction whose execution is to be confirmed.

None of them executes, cancels or transmits a transfer at the bank. They record in Scribee what you observe on the account. Confirming records the payment of the instruction's invoice; undoing removes what the confirmation recorded, and nothing else; declaring a non-execution releases the invoice's reserved amount.

OperationScopeIdempotency-Key
GET /api/v1/payment_batches/{id}/settlement/candidatesreadnot applicable
POST /api/v1/payment_batches/{id}/settlement/suggestions/{suggestion_id}/dismisswriteaccepted, optional
POST /api/v1/payment_batches/{id}/settlement/suggestions/{suggestion_id}/confirmwriterequired
POST /api/v1/payment_batches/{id}/settlement/confirmwriterequired
POST /api/v1/payment_batches/{id}/settlement/instructions/{payment_instruction_id}/undowriterequired
POST /api/v1/payment_batches/{id}/settlement/instructions/{payment_instruction_id}/not_executedwriterequired

The key is required on confirming and undoing because each reverses the other: replaying a confirmation after an undo would confirm again. It is also required on the not-executed declaration, which releases the invoice's reserved amount. With the same key, a replay returns the stored response.

Dismissing a suggestion​

curl -X POST https://app.scribee.tech/api/v1/payment_batches/7301/settlement/suggestions/91204/dismiss \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN"

Dismissing says "this operation did not settle this instruction". The 200 response carries the suggestion, whose state is now dismissed.

Nothing else changes: not the instruction's status, not the batch's state, not the invoice, not the books. The suggestion disappears from the row, and this pair is never suggested again. Dismissing a suggestion already dismissed answers the same 200. A suggestion withdrawn in the meantime by a computation or by a confirmation is refused with a 422 operation_failed: it is no longer in front of anyone.

Searching for an operation outside the window​

curl "https://app.scribee.tech/api/v1/payment_batches/7301/settlement/candidates?payment_instruction_id=61005&from=2026-09-01&to=2026-10-31" \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN"
{
"data": [
{
"bank_operation_id": 30405,
"operation_date": "2026-10-13",
"amount_match": "exact",
"day_offset": 45
}
]
}

A debit that arrived late, or a statement imported months afterwards, falls outside the review window. This search covers the dates you choose, with the same controls as the suggestions: the account the batch debits, an outgoing operation, the same currency, the exact amount, and an operation no other instruction and no invoice has taken.

  • payment_instruction_id, from and to are required; dates are written YYYY-MM-DD.
  • to is not earlier than from, and the range covers at most 366 days, bounds included.
  • The instruction belongs to the batch, and a suggestion could be computed for it: it is not rejected, not already reconciled, and its batch reached the bank.

Results come out by ascending operation date, unpaginated. day_offset counts the days from the instruction's reference date. Nothing is recorded: a result is not a suggestion, does not appear in the settlement view and will not be suggested later. To keep it, confirm it directly.

A refused parameter answers a 422 validation_failed, with details keyed by the parameter's name.

Confirming an instruction's settlement​

Confirming records the payment of the instruction's invoice and moves it to settled. Nothing goes to the bank, and the undo reverses what the confirmation recorded.

For a suggestion, address it by its id:

curl -X POST https://app.scribee.tech/api/v1/payment_batches/7301/settlement/suggestions/91204/confirm \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
-H "Idempotency-Key: 3c9e7a12-5f0b-4d6e-8a41-72b9c0d5e813"

For a result of the manual search, name the pair in the body:

curl -X POST https://app.scribee.tech/api/v1/payment_batches/7301/settlement/confirm \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
-H "Idempotency-Key: 9a41d2c7-0e3b-4f58-b6a2-5c18e7f094d3" \
-H "Content-Type: application/json" \
-d '{ "payment_instruction_id": 61005, "bank_operation_id": 30405, "expected_version": 6 }'

Both forms do the same thing:

  • The pair is checked again at confirmation time, with the suggestion controls, over the operation's own date: the window limits suggestions, never a confirmation. The operation must settle no other instruction and must not have been reconciled against another invoice.
  • The invoice is paid through the bank reconciliation. If the operation was already reconciled against that same invoice, that reconciliation and its payment are adopted, never duplicated. An instruction that pays no invoice is linked on its own, with no payment: its invoice_payment_id stays null.
  • While that link stands, the operation's allocations change only by undoing that settlement. The operation cannot be settled against any invoice other than the instruction's - against none, for an instruction that pays no invoice -, and its allocations can be neither undone nor re-sized: POST and DELETE on /api/v1/bank_operations/{id}/reconciliation, and PATCH /api/v1/bank_allocations/{id}, answer with a 422 operation_failed (Allocations and reconciliation).
  • The instruction becomes settled. When the batch is submitted and every one of its instructions has a final status, it moves to completed or partially_completed, and payment_batch.updated is emitted (Webhooks).
  • The confirmed suggestion, and every other suggestion of the instruction or of the operation, disappear from the settlement view.

The response is a 200 carrying the instruction's settlement row, matched. Confirming the pair already confirmed answers the same 200, writing nothing, whatever version is sent.

The body may carry expected_version, the batch's version as you read it; it is optional on both forms. A batch that changed since is refused with a 409 stale_version, and nothing is confirmed.

Undoing a confirmed settlement​

curl -X POST https://app.scribee.tech/api/v1/payment_batches/7301/settlement/instructions/61005/undo \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
-H "Idempotency-Key: d6b0f3a8-2c71-4e95-9f14-8e3a5b27c160"

Undoing refunds nothing and cancels no transfer at the bank. The undo removes the link between the instruction and its operation, and only what the confirmation derived from it:

  • The invoice payment the confirmation recorded is removed, and only that one: the one the instruction names by its invoice_payment_id. An accounting entry already exported is never rewritten: a correction entry is posted. A reconciliation of the operation with the invoice that existed before the confirmation, and its payment, stay in place, as does any payment the confirmation did not record: the undo then removes only the link.
  • The bank's status reports are kept.
  • If the bank reported executing the instruction, it stays settled, and the batch keeps its state. This holds too when that report arrived after the confirmation: it does not change the instruction's status, already settled, but it is kept.
  • Otherwise the instruction returns to pending - execution to confirm. It keeps its invoice's amount reserved, and it is never handed to the bank a second time. A completed or partially_completed batch returns to submitted, and payment_batch.updated is emitted. The settlement row then carries execution_to_confirm_at, the instant of the undo.

execution_to_confirm_at does not mean the payment was cancelled. A missing bank operation does not prove the transfer was not executed: its execution is simply no longer demonstrated. The field does not change the row's state. It returns to null when the bank reports the instruction executed, which becomes settled, when the bank rejects it, which becomes rejected, or when a bank operation is confirmed for it. A new pending status reported by the bank leaves it in place. On a sepa_file batch, you may also declare the instruction not executed.

The response is a 200 carrying the instruction's settlement row. Scribee then recomputes the batch's suggestions in the background: the operation may be suggested again, under a new id, unless you had dismissed it. A new confirmation moves the instruction back to settled.

Undoing an instruction that is not reconciled answers the same 200, writing nothing, whatever version is sent. The body accepts the same optional expected_version as the confirmation.

Declaring an instruction not executed​

curl -X POST https://app.scribee.tech/api/v1/payment_batches/6100/settlement/instructions/78012/not_executed \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
-H "Idempotency-Key: 5e2c8b71-a0d4-4f39-8c6e-1b7d93f24a05" \
-H "Content-Type: application/json" \
-d '{ "reason": "file_not_uploaded", "note": "The SEPA file was never uploaded to the bank portal.", "expected_version": 9 }'

An instruction whose execution is to be confirmed - its execution_to_confirm_at is set - keeps its invoice's amount reserved until something resolves it. What may resolve it depends on the batch's channel:

Batch channelWhat resolves an execution to confirm
hosted_consentWhat the bank reports - the instruction's execution or rejection -, or the confirmation of a bank operation. A not-executed declaration is refused
sepa_fileThe confirmation of a bank operation, or your not-executed declaration: no bank answers Scribee about a file you transmitted yourself

A missing bank operation does not prove the payment was not executed. Finding no debit on the account is not enough: declare a non-execution when you observe it, and say which one.

FieldWhat it carries
reasonRequired. Why the payment was not executed: file_not_uploaded, the file was never uploaded to the bank; rejected_by_bank_portal, the bank portal rejected it; cancelled_at_bank, it was cancelled at the bank
noteRequired. Your explanation, kept with the declaration: 2000 characters at most, once the surrounding spaces are removed
expected_versionOptional. The batch's version as you read it; a batch that changed since is refused with a 409 stale_version, and nothing is declared

The instruction must belong to the batch, and its execution must be to be confirmed. An instruction whose settlement is confirmed cannot be declared: undo that settlement first.

The declaration:

  • moves the instruction to rejected, for good, and resets its execution_to_confirm_at to null. The settlement row carries not_executed_declaration, which tells your declaration apart from a refusal reported by the bank;
  • releases the invoice's reserved amount: a new batch, created in Scribee, may pay it;
  • hands nothing to the bank: the declared instruction is never handed over a second time, and no new batch is created for you;
  • keeps the bank's status reports;
  • is recorded in your application's name, with its date;
  • changes the batch's version. When the batch is submitted and every one of its instructions now has a final status, it moves to rejected or partially_completed, and payment_batch.updated is emitted (Webhooks).

The response is a 200 carrying the instruction's settlement row:

{
"data": {
"payment_instruction_id": 78012,
"state": "unmatched",
"requires_review": false,
"bank_operation_id": null,
"invoice_payment_id": null,
"execution_to_confirm_at": null,
"not_executed_declaration": {
"reason": "file_not_uploaded",
"note": "The SEPA file was never uploaded to the bank portal.",
"declared_at": "2026-09-29T10:15:00+02:00"
},
"provider_evidence_conflict_at": null,
"late_provider_events": [],
"candidate_bank_operation_ids": [],
"matched_on": [],
"suggestions": []
}
}

Sending the same declaration again - same reason, same note - on an instruction that already carries it answers the same 200, writing nothing, whatever version is sent. A different declaration is refused: the instruction's execution is no longer to be confirmed.

The refusals​

StatuscodeWhen
422validation_failedThe Idempotency-Key is missing on a confirmation, an undo or a declaration; expected_version does not read as an integer; on POST .../settlement/confirm, the instruction does not belong to the batch or the operation does not belong to the batch's company; on a declaration, reason or note is present but not a JSON string, reason is not one of the three values, or note is empty or exceeds 2000 characters. details names each refused field
422operation_failedThe pair is refused, and message says why: the suggestion is no longer proposed, the instruction is already reconciled with another operation or cannot be settled, the operation already settles another instruction, is reconciled against another invoice or fails the controls, or the bank reconciliation refuses the invoice payment. On an undo: the recorded payment cannot be removed. On a declaration: the batch is not a sepa_file batch, or the instruction's execution is not to be confirmed. On all of them: the batch is being updated, try again in a moment
409stale_versionexpected_version no longer matches the batch's version. Nothing was confirmed, undone or declared; details.current_version carries the current version and details.payment_batch the batch
409idempotency_key_reuseThe same Idempotency-Key accompanies a different request
409idempotency_request_in_progressA call carrying the same Idempotency-Key is still in progress
404not_foundThe batch is not reachable by your grants, or the suggestion or instruction in the path does not belong to this batch

The batch list and the batch read accept include, with one or two values separated by a comma:

includeKey addedContent
payment_instructionspayment_instructionsAll of the batch's instructions, unpaginated, in ascending order of their identifier
bank_accountbank_accountThe debited account, with the full payload of Bank accounts

Without include, these keys are absent - not present as null. Any other value is ignored. Instructions accept no include.

Amounts​

total_amount and amount are JSON numbers rounded to two decimals, not strings: 1250.50 is written 1250.5. total_amount is the sum of the amounts of the batch's instructions.

Preparing, approving and handing over a batch​

Seven operations take a batch from its creation to its handover. They apply the rules of the Scribee payment centre: a batch prepared through the API carries the same instructions, the same identifiers and the same refusals as a batch prepared on screen.

The complete flow​

  1. POST /api/v1/workspaces/{workspace_id}/companies/{company_id}/payment_batches creates the batch, draft.
  2. PATCH /api/v1/payment_batches/{id} corrects it, as long as it is draft.
  3. POST /api/v1/payment_batches/{id}/submit_for_approval moves it to pending_approval.
  4. POST /api/v1/payment_batches/{id}/approve moves it to approved. POST /api/v1/payment_batches/{id}/refuse sends it back to draft, where it is corrected before being submitted again.
  5. POST /api/v1/payment_batches/{id}/submit hands an approved hosted_consent batch over to its channel. A sepa_file batch is handed over by POST /api/v1/payment_batches/{id}/sepa_export (Generating and downloading the SEPA file).
  6. POST /api/v1/payment_batches/{id}/consent_session publishes, for a hosted_consent batch, the page where the account holder gives their consent (Resuming the consent).
  7. GET /api/v1/payment_batches/{id} follows the batch until it leaves approved, or the payment_batch.updated event announces it to you (Being notified of a change of state).

None of these steps proves that a transfer was executed, not even the handover: only the batch's state and its instructions' status say anything about it (The states of a batch).

Version and idempotency​

OperationIdempotency-Keyexpected_versionSuccess
POST /api/v1/workspaces/{workspace_id}/companies/{company_id}/payment_batchesrequirednot applicable201
PATCH /api/v1/payment_batches/{id}accepted, optionaloptional200
POST /api/v1/payment_batches/{id}/submit_for_approvalrequiredoptional200
POST /api/v1/payment_batches/{id}/approverequiredrequired200
POST /api/v1/payment_batches/{id}/refuserequiredoptional200
POST /api/v1/payment_batches/{id}/submitrequiredrequired202
POST /api/v1/payment_batches/{id}/consent_sessionignorednot applicable200

All seven require the write scope, and nothing else: approval has no scope of its own. A token carrying write, with a grant on the batch's workspace, can approve the batch it prepared. Scribee does not check that the approval comes from a different OAuth client than the preparation; if your organisation separates these two roles, that separation is yours to enforce.

expected_version is the batch's version as you read it, sent in the request body. On the four transitions, the body carries nothing else. A batch that changed since you read it is refused with a 409 stale_version, and nothing is written or sent; details.current_version carries the current version and details.payment_batch the batch as GET /api/v1/payment_batches/{id} publishes it. Read it again, check that the action still makes sense, then resend the call with details.current_version. An expected_version that is required and missing, or that does not read as an integer, is refused with a 422 validation_failed, with details.expected_version.

With the same Idempotency-Key, a replay returns the stored response: a batch is not created twice, and a handover never reaches the channel twice. Without it, two identical creations create two batches. Only a success is stored: a refusal does not consume the key, and the corrected call may be resent with the same key.

Creating a draft​

curl -X POST https://app.scribee.tech/api/v1/workspaces/YOUR_WORKSPACE_ID/companies/YOUR_COMPANY_ID/payment_batches \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
-H "Idempotency-Key: 2b7e40a1-9c31-4d55-8f0b-6a1c2e93d770" \
-H "Content-Type: application/json" \
-d '{
"bank_account_id": 812,
"name": "Fournisseurs 2026-08",
"channel": "hosted_consent",
"execution_date": "2026-08-29",
"instructions": [
{
"invoice_document_id": 88120,
"amount": 830.0,
"beneficiary_name": "Papeterie Martin SARL",
"beneficiary_iban": "FR7630006000011234567890189",
"reference": "FA-2026-0310"
}
]
}'

The response is a 201 carrying the batch, draft, in the shape of its read (Reading a batch).

  • bank_account_id, channel and instructions are required; name and execution_date are optional. channel is hosted_consent or sepa_file.
  • bank_account_id names a bank account of the company in the path (Bank accounts). The batch takes that account's currency, or EUR when the account carries none: the body carries no batch currency.
  • instructions carries at least one line. Each line requires amount, beneficiary_name and beneficiary_iban. invoice_document_id names the company's invoice that the line settles, or is null for a payment that settles no Scribee invoice. reference is the reference the beneficiary sees.
  • A line's currency_code is optional and defaults to the batch currency; any other value is refused. A line that settles an invoice in another currency is refused too: Scribee never converts an amount.
  • A line's execution_date is optional and defaults to the batch's.
  • beneficiary_iban is the full IBAN. Spaces are accepted and removed; the format and the check digits are verified. It is never returned: neither this response nor any other publishes more than beneficiary_iban_masked and beneficiary_iban_last4 (A batch's instructions).
  • Scribee assigns each line its end_to_end_id.

A draft reserves nothing and moves no money. The amount of the settled invoices is reserved only at the handover.

A bank_account_id or an invoice_document_id that does not name a record of this company is refused with a 422 invalid_argument, whether the record belongs to someone else or does not exist: this refusal reveals nothing about records that are not yours.

Editing a draft​

curl -X PATCH https://app.scribee.tech/api/v1/payment_batches/7301 \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{ "execution_date": "2026-09-01", "expected_version": 1 }'

The response is a 200 carrying the edited batch.

  • Only a draft batch can be edited. Any other state is refused with a 422 batch_not_submittable, whatever version is sent. To correct a pending_approval batch, refuse its approval: it returns to draft.
  • Only the fields sent are written. A field absent from the body keeps its value; "name": null clears the label.
  • instructions, when sent, replaces the whole set of lines: a line absent from the list is removed. The new lines receive their end_to_end_id, numbered again from the start.
  • Changing bank_account_id changes the batch currency. The lines kept must be in that currency, otherwise the call is refused: send instructions along with the new account.
  • Changing execution_date carries along the lines that had the batch's previous date; a line that had its own date keeps it.

An edit does not change the batch's state, and therefore does not emit payment_batch.updated.

Submitting for approval, approving, refusing​

curl -X POST https://app.scribee.tech/api/v1/payment_batches/7301/approve \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
-H "Idempotency-Key: 5d1f8c3e-7a24-4b90-9e61-0c3b7f2a8d45" \
-H "Content-Type: application/json" \
-d '{ "expected_version": 3 }'
OperationFrom stateTo stateOn a batch already in the target state
POST /api/v1/payment_batches/{id}/submit_for_approvaldraftpending_approvalRefused: 422 batch_not_submittable
POST /api/v1/payment_batches/{id}/approvepending_approvalapproved200, nothing changes
POST /api/v1/payment_batches/{id}/refusepending_approvaldraft200, nothing changes

The response is a 200 carrying the batch in its new state.

  • Submitting for approval requires at least one instruction, all still valid. Otherwise the call is refused with a 422 validation_failed, and details.instructions names the end_to_end_id of the lines that no longer are.
  • Approving fills in approved_at. The batch's total amount is then frozen.
  • Refusing sends the batch back to draft, where it can be edited again.
  • From any other state, the transition is refused with a 422 batch_not_submittable.

Each transition emits payment_batch.updated; a call that changes nothing emits nothing (Webhooks).

Knowing who approved or refused a batch​

Each approval and each refusal records who decided it: the user, for a decision taken in the Scribee payment centre; the API application the token was issued to, for a call to POST /api/v1/payment_batches/{id}/approve or POST /api/v1/payment_batches/{id}/refuse. The batch publishes it in two fields, present in all of its responses:

FieldContent
approved_byThe batch's approval, or null until it has been approved
refused_byThe most recent refusal of approval, or null if the batch's approval was never refused. A refused batch returns to draft and may be submitted and approved again, so refused_by can name a refusal earlier than approved_by

Each carries:

FieldContent
actor_typeuser for a decision taken in the Scribee payment centre, api_client for a decision taken through this API
nameWho decided, as named at that time: for user, the user's first and last name, or, if they set neither, a fixed label that does not identify them, in the platform's default locale (French: "Utilisateur sans nom"); for api_client, the name of the API application. It is never an email address
decided_atThe date and time of the decision. On approved_by, it is the value of approved_at
  • Only a decision that changes the batch's state is recorded. A call on a batch already in the target state answers 200 and records nothing. Of two simultaneous decisions, only the one that moves the batch is recorded.
  • No decider is inferred after the fact. A decision taken before Scribee recorded its decider has none: the batch then publishes approved_by as null although approved_at is filled in, and such a refusal does not appear in refused_by.
  • Whoever submits a batch may also approve it. Scribee does not require the approval to come from a different user or application than the submission.
curl -X POST https://app.scribee.tech/api/v1/payment_batches/7301/submit \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
-H "Idempotency-Key: 8e2a6f14-3c95-4d07-b1e8-9f4a2c6d0b73" \
-H "Content-Type: application/json" \
-d '{ "expected_version": 4 }'

submit hands over to its channel an approved batch whose channel is hosted_consent, and which carries no handover attempt yet. A sepa_file batch is refused with a 422 batch_not_submittable: it is handed over by POST /api/v1/payment_batches/{id}/sepa_export.

Before calling the channel, Scribee reserves the amount of each settled invoice, as for a SEPA file. A refused reservation answers a 422 validation_failed, with details keyed payment_instructions.<id> and valued already_reserved, exceeds_remaining_due or currency_mismatch; the batch stays approved, with no attempt recorded, and nothing is sent.

Scribee can also refuse the batch before anything is sent: when the channel cannot express it as it stands, or when the request to the provider cannot be built on our side. The call then answers a 422 validation_failed, with details.instructions; nothing is sent to the provider, but the batch moves to failed with submission_refused_by at local, as from the payment centre, its instructions move to submission_failed, and payment_batch.updated is emitted. This refusal does not consume the Idempotency-Key: a new call is processed again, but a failed batch is never handed over.

The response is a 202 as soon as the call reached the channel, whatever the channel answered. A 202 never means that money moved. Read the batch it carries:

  • a payment request awaiting consent leaves the batch approved, with submission_status at awaiting_consent: the account holder must still authorise it on their bank's page;
  • an unknown outcome leaves the batch approved, with submission_status at unknown;
  • a refusal by the provider moves the batch to failed, with submission_refused_by at provider, error_code and error_message;
  • a channel that takes the batch moves it to submitted, or further if its answer already carries the outcome of each instruction.

A batch that stays approved is followed like any handover under way (A handover under way whose outcome is not known). The response never carries the address of the consent page: POST /api/v1/payment_batches/{id}/consent_session publishes it.

Resending submit on a batch the channel has already taken answers the same 202, without sending anything. On a batch whose attempt is already under way without having been taken - consent pending, outcome unknown -, the call is refused with a 422 batch_not_submittable: Scribee never hands a batch's instructions over twice. When another request is handing over the same batch, the call is refused the same way, and its message invites you to read the batch again shortly. When no channel is available for this batch, the call is refused with a 422 operation_failed. None of these refusals sent anything.

curl -X POST https://app.scribee.tech/api/v1/payment_batches/7301/consent_session \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN"
{
"data": {
"payment_batch_id": 7301,
"state": "pending",
"consent_url": "https://consent.example.com/payments/7Hq2/authorize",
"consent_url_expires_at": null
}
}

A hosted_consent batch that was handed over waits for the account holder to authorise the payment on their bank's page. POST /api/v1/payment_batches/{id}/consent_session publishes the address of that page. Consent is given in a browser, by the account holder: no API call gives it in their place. Pass consent_url to that person, who opens it in their browser.

  • There is a consent only after submit. A batch that carries no handover attempt yet, or a sepa_file batch, is refused with a 422 batch_not_submittable, without the payment provider being asked.
  • Nothing is created, and nothing is sent a second time. The response is a 200, with no id and no timestamp: the consent is not a stored resource. Each call asks the provider again about the payment request submit created; a batch whose handover is already resolved is answered from its state, without asking it.
  • That query can move the batch. A consent the provider has since accepted, let expire or refused takes the batch out of approved, like any answer from the channel, and payment_batch.updated is emitted.
  • The write scope is required.
stateWhat it means
pendingThe account holder has not consented yet, or the provider's answer is not known. The batch stays approved
completedThe provider took the payment. That is not proof that money moved: follow the batch and its instructions
expiredThe provider reports that the consent expired without being given. The batch is failed
failedThe provider refused the consent for another reason. The batch is failed

consent_url is filled in only when state is pending and the provider supplied one; it is null otherwise. consent_url_expires_at is null unless the provider gives a deadline, and null does not mean the address never expires. Time changes nothing: no state changes, and no reserved amount is released, because a delay has elapsed.

consent_url is a bearer capability: whoever holds it can open the consent page. Do not store it, do not log it, and ask for a fresh one rather than reusing an old one. The response carries Cache-Control: no-store, and the endpoint takes no Idempotency-Key: a key that is sent is ignored, and nothing is stored or replayed.

Six calls per minute, per OAuth application and per batch. Beyond that, the call is refused with a 429 rate_limited, with the Retry-After header in seconds: wait that long before retrying. Nothing is done, and the provider is not asked. Every call that reaches the batch counts, a 422 refusal included; a 404 does not count. Other batches, and other applications, are counted separately. To follow the outcome of the consent, read GET /api/v1/payment_batches/{id} or wait for payment_batch.updated, rather than calling consent_session in a loop: ask for an address when the account holder has to open the page.

The refusals of the writes​

StatuscodeWhen
422validation_failedThe Idempotency-Key is missing where it is required; expected_version is missing where it is required or does not read as an integer; a body field is refused - instructions empty or not a list of objects, a line in a currency other than the batch's or its invoice's, a malformed IBAN, an unknown channel, a missing bank_account_id, an execution_date not in the YYYY-MM-DD format; on submit_for_approval, a batch with no instruction or with an instruction that is no longer valid; on submit, a refused reservation, or a batch Scribee refuses before anything is sent, which then moves to failed with submission_refused_by at local. details names the field, and a line is written instructions[0] for the first one
422invalid_argumentbank_account_id or an invoice_document_id does not name a record of the batch's company. This refusal carries no details
422batch_not_submittableThe batch is not in the state the operation requires: PATCH outside draft, a transition from a state other than its from state, submit on a batch that is not approved, that already carries an attempt, that another request is handing over, or whose channel is sepa_file; consent_session on a batch that carries no handover attempt yet, or whose channel is sepa_file. This refusal carries no details; message gives the cause
422operation_failedOn submit, no channel is available for this batch. Nothing was sent
409stale_versionexpected_version no longer matches the batch's version. Nothing was written or sent; details.current_version carries the current version and details.payment_batch the batch
409idempotency_key_reuseThe same Idempotency-Key accompanies a different request
409idempotency_request_in_progressA call carrying the same Idempotency-Key is still in progress
429rate_limitedOn consent_session, more than six calls in a minute by the same application on the same batch. Nothing was done; retry after the delay in the Retry-After header, in seconds

Generating and downloading the SEPA file​

A sepa_file batch leaves Scribee as a SEPA credit transfer file in the pain.001 format, which you hand over to your bank yourself. POST /api/v1/payment_batches/{id}/sepa_export generates it; GET /api/v1/payment_batches/{id}/sepa_export reads it and serves it.

A file generated, downloaded or handed over to your bank does not mean that the transfers were executed. Neither of these two operations reports what the bank did, and neither records the payment of an invoice. The batch moves to submitted once the handover of the file is finalised, and leaves it only through the confirmation of its instructions' settlement: Scribee receives no answer from the bank about a SEPA file.

Generating the file​

curl -X POST https://app.scribee.tech/api/v1/payment_batches/6100/sepa_export \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
-H "Idempotency-Key: f0a2c518-4b73-49de-9c21-3e8a5d70b1c9"

The Idempotency-Key header is required. The batch must be approved and its channel must be sepa_file. Generation starts the batch's handover through the same controls as the payment centre - approval, version, reservation, single attempt: once the call is accepted, the batch carries a submitted_at and its instructions can no longer be handed over a second time.

The body is optional. It may carry expected_version, the batch's version as you read it:

{ "expected_version": 6 }

Without expected_version, no version is checked. With it, a batch that changed since you read it is refused with a 409 stale_version and no file is generated. A value that does not read as an integer is refused with a 422 validation_failed, with details.expected_version.

The answer is a 202 carrying the export resource, including for a batch whose export was already requested: the call then answers with the existing export, whatever version is sent, and never generates a second one. Generation itself changes the batch's version: once the export exists, sending the call again with the version read before generation therefore answers that export, not a 409. Replaying the same Idempotency-Key returns the stored response.

{
"data": {
"id": 4400,
"payment_batch_id": 6100,
"state": "generating",
"progress": 100,
"retryable": false,
"started_at": "2026-08-27T10:05:40+02:00",
"finished_at": "2026-08-27T10:05:40+02:00",
"format": "pain.001.001.09",
"filename": "sepa-20260829-SCB4F1A9C07E2B84D5A9E36C1F0B7D2A845.xml",
"byte_size": 4812,
"sha256": "9f2c4e71a0b3d58e6c17f2a94b0e3d8c5a61f7e20b94c3d18e5a7f06c2b94d31",
"generated_at": "2026-08-27T10:05:40+02:00",
"served_count": 0,
"first_served": null,
"last_served": null
}
}

progress, started_at and finished_at describe the production of the file, not its handover: only state says whether the file can be downloaded. retryable is always false.

Waiting for the file​

Poll GET /api/v1/payment_batches/{id}/sepa_export without asking for XML: it answers the same JSON resource. state is generating until the handover of the file is finalised, then available - the batch is then submitted. These are its only two values. The GET never generates a file: on a batch whose export was never requested, it answers 404.

Downloading the file​

curl https://app.scribee.tech/api/v1/payment_batches/6100/sepa_export \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
-H "Accept: application/xml" \
-o sepa.xml

With Accept: application/xml and a state of available, the answer is the file itself, as an attachment named after filename. Before that, the same request answers a 200 carrying the JSON resource: check the Content-Type of the response before saving it as a file. Only a 200 carries XML: an error always answers in JSON, even with Accept: application/xml.

Once available, the file no longer changes: its bytes, its sha256 and its generated_at are fixed. Compare sha256 with the SHA-256 digest of the file you received - that is what proves the file sent to your bank is the one Scribee produced. A file whose bytes no longer match sha256 is never served.

Knowing when the file was served​

Every time the bytes of the file are served - downloaded from the Scribee payment centre, or through GET /api/v1/payment_batches/{id}/sepa_export with Accept: application/xml -, Scribee records it. The export resource publishes it in three fields, present in all its responses, the POST one included:

FieldContent
served_countHow many times the file was served, across all channels; 0 if it never was
first_servedThe first download, or null if the file was never served
last_servedThe last download, or null if the file was never served. It is the same as first_served when the file was served only once

first_served and last_served each carry:

FieldContent
served_atThe date and time the file was served
channelweb for a download from the Scribee payment centre, api for a download through this API
served_toWho the file was served to, as named at that time: on web, the user's first and last name, or, if they set neither, a fixed label that does not identify them, in the platform's default locale (French: "Utilisateur sans nom"); on api, the name of the API application. It is never an email address

After one download from the payment centre and another through the API, the resource reads:

{
"data": {
"id": 4400,
"payment_batch_id": 6100,
"state": "available",
"progress": 100,
"retryable": false,
"started_at": "2026-08-27T10:05:40+02:00",
"finished_at": "2026-08-27T10:05:40+02:00",
"format": "pain.001.001.09",
"filename": "sepa-20260829-SCB4F1A9C07E2B84D5A9E36C1F0B7D2A845.xml",
"byte_size": 4812,
"sha256": "9f2c4e71a0b3d58e6c17f2a94b0e3d8c5a61f7e20b94c3d18e5a7f06c2b94d31",
"generated_at": "2026-08-27T10:05:40+02:00",
"served_count": 2,
"first_served": {
"served_at": "2026-08-27T11:02:13+02:00",
"channel": "web",
"served_to": "Claire Dupont"
},
"last_served": {
"served_at": "2026-08-28T09:14:50+02:00",
"channel": "api",
"served_to": "Compta Connect"
}
}
}

Served only means that the bytes of the file were sent in an HTTP response to that user or that application. It proves neither that the file was received, nor that it was handed over or transmitted to your bank, nor that the bank executed the transfers. Only the batch's state and its instructions' status report what the bank did.

Downloading the file changes nothing else: neither the batch, nor its instructions, nor the export, nor the payment of an invoice. Its bytes, its sha256 and its generated_at stay the ones published. Repeating the download serves the same file and increments served_count each time. Not counted: reading the JSON resource, a 404, an operation_failed refusal, or an export still generating. If a download cannot be recorded, the file is not served: the answer is an error, never the file.

The required scope​

Both operations require the write scope, the GET included: the file carries the beneficiaries' full IBANs, which the JSON surface never publishes. A token carrying only read receives a 403.

The refusals​

StatuscodeWhen
422validation_failedThe POST has no Idempotency-Key; or the batch fails pain.001 business validation, and details.base carries the recorded diagnosis: each failing rule, followed by what it targets, batch or an instruction's end_to_end_id; or an instruction cannot reserve the amount of its invoice, and details is keyed payment_instructions.<id>, with already_reserved, exceeds_remaining_due or currency_mismatch as its value
422validation_failedexpected_version does not read as an integer; details.expected_version says so
422batch_not_submittableThe batch is not approved, its channel is hosted_consent, its handover attempt ended without a file, or a handover attempt is still in progress and has not yet produced its export. This refusal carries no details
422operation_failedOn the GET with Accept: application/xml: the stored file is gone, or no longer matches its sha256
409stale_versionexpected_version no longer matches the batch's version, and no export exists yet. Nothing was generated; details.current_version carries the current version and details.payment_batch the batch as GET /api/v1/payment_batches/{id} publishes it
409idempotency_key_reuseThe same Idempotency-Key comes with a different request
409idempotency_request_in_progressA call carrying the same Idempotency-Key is still running

A handover attempt still in progress is recognisable by its message:

{
"error": "unprocessable_entity",
"code": "batch_not_submittable",
"message": "Une tentative de remise de ce lot de paiement est toujours en cours ; cet appel n'a rien généré. Relisez l'export dans quelques instants."
}

Send the call again a little later: as soon as that attempt has produced its export, the POST answers a 202 with it.

On a 409 stale_version, re-read the batch from details, check that it should still be handed over, then send the call again with details.current_version as expected_version.

A 404 says that the batch is not reachable by your grants or, on the GET, that no export was ever requested for this batch.

A 422 refusal does not consume the Idempotency-Key. That does not make every batch generatable again. A reservation refusal leaves the batch approved, with no attempt recorded: fix the cause and send the call again. A pain.001 validation failure is final: the batch moves to failed, with submission_refused_by at local, and a new call answers batch_not_submittable.

The errors​

StatuserrorWhen
400bad_requestA page that is not an integer greater than or equal to 1, or a page beyond the last page of a non-empty collection
403forbiddenThe token does not carry the required scope - read, or write on the batch writes, on sepa_export and on the POST operations of settlement -, your OAuth client holds no grant on this workspace, or bank reconciliation is disabled there
404not_foundThe company, the batch, the instruction or the suggestion is not reachable by your grants

A page beyond the end:

{
"error": "bad_request",
"message": "Le numéro de page dépasse le nombre de pages disponibles"
}

A page that is not a valid integer carries the message "Le numéro de page doit être un entier supérieur ou égal à 1". On an empty collection no page number overflows: the answer is a 200 with an empty collection.

On the by-identifier paths, reachability is settled before the feature. A batch or an instruction your grants do not cover answers 404, never 403, even when bank reconciliation is disabled in its workspace. A 403 on these paths therefore never confirms that an identifier exists outside your grants.

{
"error": "not_found",
"message": "La ressource demandée est introuvable"
}

API reference​