Bank syncs
A sync (bank_sync) is a run that fetches a company's bank data from our aggregator: accounts discovered, operations imported. You start one, then you poll it.
A bank connection is what grants the access (Bank connections); a sync is what uses it.
The thing to understand before anything else
A 202 never means the work finished. It means a run exists and it is yours. Nothing has been counted yet, nothing has been imported yet.
Polling the run is how you learn what happened - including failure. Terminal failure is reported on the run itself, never only in a log you cannot see.
Starting a sync
A token carrying the write scope is required, along with an Idempotency-Key header.
curl -X POST https://app.scribee.tech/api/v1/workspaces/YOUR_WORKSPACE_ID/companies/YOUR_COMPANY_ID/bank_syncs \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
-H "Idempotency-Key: 9d2b1f30-6c44-4b8b-9d55-1f0a7e2c9a31"
There is no request body. A sync covers every connection of the company: the worker walks all of them, and we would rather not offer you a narrowing parameter it would not honour. The response's bank_connection_id is therefore null today; the key is present so the shape does not change when a targeted run becomes possible.
202 response:
{
"data": {
"id": 9012,
"company_id": 34,
"bank_connection_id": null,
"bank_account_id": null,
"kind": "manual",
"state": "pending",
"progress": null,
"error_code": null,
"error_message": null,
"retryable": false,
"started_at": null,
"finished_at": null,
"accounts_discovered_count": 0,
"operations_imported_count": 0,
"created_at": "2026-08-21T11:15:00+02:00",
"updated_at": "2026-08-21T11:15:00+02:00"
}
}
One run at a time per company. If one is already in flight the call is refused with 422 and the code sync_in_progress: wait for it rather than starting another. We do not hand back the running run's id - you received it from your own 202.
Polling the run
A read token is enough.
curl https://app.scribee.tech/api/v1/bank_syncs/9012 \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN"
state takes one of these five values:
state | What it means |
|---|---|
pending | The run is accepted and waiting its turn |
running | It is under way |
completed | It finished with a complete data set |
partial | It finished knowing its data was incomplete |
failed | It failed |
partial is not a rounding of completed. The run finished with a result set it knows to be incomplete: data is genuinely missing, and another sync is worth running. Treating partial as a success is exactly the mistake this value exists to prevent.
The incompleteness does not always have the same cause - a page the run did not read, an operation it refused to store - and partial does not tell you which. It tells you what is enough to act on: data is missing. No bank movement is lost for all that, an operation that was refused stays within the scope of later syncs and is picked up as soon as it becomes usable; a run can therefore stay partial for as long as the datum in question has not changed at the aggregator.
An operation not imported because a bank statement already holds its day is not a cause of partial. Nothing is missing: for that day the account's operations are the statement's lines (see Bank statements). If the statement gives that day back later and no other statement line holds it, the next sync re-reads the account from the start and imports it.
kind says why the run exists: initial (the first backfill after a connection), webhook (the aggregator told us something changed), scheduled (the periodic refresh), manual (you asked for it).
include=bank_connection adds the id and status of the connection involved.
When a run fails
error_code is operation_failed as soon as the run carries a diagnosis, and null otherwise: a failed run always carries one, a partial run may or may not, depending on the cause of its incompleteness. retryable says whether running the same sync again could plausibly end differently.
What to do about a failure is read off the connection, not the run. If the bank withdrew its authorisation, it is the connection's status that becomes expired, and the remedy is reconnect. The run tells you it failed and whether another attempt makes sense; the connection tells you why and what to do.
progress and error_message are currently always null: nothing produces a percentage, and an exception's text has no place on a published field. We would rather publish nothing than a value you would build a progress bar on.
Errors
| Status | code | When |
|---|---|---|
422 | validation_failed | The Idempotency-Key header is missing |
422 | sync_in_progress | A run is already in flight for this company |
409 | idempotency_key_reuse | The same Idempotency-Key has already served a different body |
404 | - | The run is not reachable by your grants |