- Find the provider and the credentials it needs.
- Create the integration.
- Test the saved connection.
- Optionally, choose what it reads.
- Optionally, estimate a sync.
- Start a sync and follow its progress.
operationId, so any generated client reads the same.
1. Find the provider
GET /v1/integration-providers lists every provider with what you need to connect it:
connection.methodiscredentialswhen you send field values yourself, orprovider_authorizationwhen the customer signs in at the provider instead of step 2. See connect at the provider.connection.fieldsdescribes each value to collect:typeis the JSON value to send (string,integerorstring_array),choiceslists the only accepted values when there is a fixed list, andrequiredsays it must be set. Asecretfield is write-only.labelis English; show your own copy and logos by providerkey.capabilitiessays whichscopefields the provider honours, and whether it supports sync estimates and realtime updates.
options_source: "accounts" is chosen from the provider’s own accounts: send the other
fields to POST /v1/integrations/accounts and pick from the returned codes (suggested marks the
likely ones).
2. Create the integration
POST /v1/integrations (Admin) saves the connection as not_tested. Creating never contacts the
provider.
POST /v1/integrations/test with the same
provider and credentials runs the same test as step 3. It is optional.
3. Test the saved connection
POST /v1/integrations/{id}/test with no body signs in, lists invoices within the scope and opens
one attachment, then saves the resulting status. A passing test makes the integration syncable.
A connection that does not work is still a 200, with result: "failed"; steps names the step
and category that failed.
4. Choose what it reads
The integration’sscope is what every sync reads, including the nightly one when auto_sync is
on. Set it with PATCH /v1/integrations/{id} (Admin). To choose from what the provider has,
GET /v1/integrations/{id}/suppliers and …/companies list them with search and paging. They are
served from the last list read from the provider: start one with POST …/suppliers/refresh (or
…/companies/refresh), then poll the list until refresh_status is no longer running.
A supplier include list reads only the listed suppliers. include_unidentified also reads
invoices whose supplier the provider does not identify, and is on unless you set it to false.
Change settings and credentials
PATCH takes any of name, enabled, auto_sync, scope and credentials. credentials is a
merge patch: fields you leave out keep their saved value and null clears an optional one. Saved
secrets are never returned; credentials.secrets_set lists the ones that have a value. New
credentials reset status to not_tested, so test again.
Send the ETag you last read as If-Match. If someone else changed the integration since, the
update is refused with 412 and nothing is saved: read it again and reapply your change.
POST /v1/integrations/{id}/test (Admin):
it is tested over the saved values and nothing is saved.
5. Estimate a sync
Wherecapabilities.estimate is true, POST /v1/integrations/{id}/sync-estimates counts the
invoices a sync with the same body would import, without importing anything. Poll it until status
is no longer running. lower_bound is true when counting stopped at the time limit, and
configuration_revision_matches turns false when the scope or credentials changed after it
started.
One estimate runs per integration: while one is running, asking again is
409 estimate_in_progress. GET …/sync-estimates?limit=1 returns it; wait for it, or cancel it
with DELETE /v1/integrations/{id}/sync-estimates/{estimate_id} and ask again. Starting a sync
cancels a running estimate once the sync is accepted; a rejected sync leaves it running.
6. Start a sync
POST /v1/integrations/{id}/syncs reads a period and imports its invoices. date_from and
date_through are both inclusive; leave them out to read the last month up to today. The response
is 202 with the sync.
scope out to read the saved scope. A scope in the body replaces the saved one for this run
only, as a whole: a dimension you leave out reads everything, rather than keeping its saved value.
A run scope has no include_unidentified or module_company_codes; its supplier include list
reads only the listed suppliers.
Without Admin, a sync or estimate must read a supplier include list: send one as scope, or leave
scope out when the saved scope is one. Otherwise it is 403.
One sync runs per integration at a time. While it runs, starting another sync, changing scope or
credentials, and deleting the integration return 409 sync_in_progress with the run in
workflow_run_id. Cancel that run, or wait, and try again. Renaming and enabling are not affected.
7. Follow progress
The syncid is also a workflow run id. Follow progress with GET /v1/workflow-runs/{id}, read
imported and failed invoices from its items, and stop it with POST /v1/workflow-runs/{id}/cancel.
See workflow runs.
GET /v1/integrations/{id}/syncs lists every sync, including the nightly ones. coverage is
partial when a sync ended without reading everything it was asked to. When retryable is true,
POST …/syncs/{sync_id}/retry completes it: with no body it continues where discovery stopped and
retries the failed invoices that can succeed on a second attempt; with item_ids it retries exactly
those invoices. The same run becomes active again.
Connect at the provider
Aprovider_authorization provider (Fortnox, e-conomic) replaces step 2: the customer signs in at
the provider, so this needs their browser and a signed-in session, not an API key.
- Generate a random 32-byte hex
browser_nonceand keep it in session storage. POST /v1/integrations/authorizationswith theprovider, thebrowser_nonce, and either aname(plus the provider’sconnection.fields) to create an integration or anintegration_idto reconnect one. Send the browser to the returnedurl.- The provider sends the browser back to your app with
integration_stateandintegration_codein the URL fragment, orintegration_errorif the customer cancelled. POST /v1/integrations/authorizations/completewithstate,codeand the samebrowser_nonce. The integration is saved asnot_tested; continue with step 3.
Realtime updates
For a provider withcapabilities.realtime, turning on auto_sync also subscribes to the
provider’s webhooks, so invoice changes arrive without waiting for the nightly sync. Turning it on
needs an enabled integration that passed its test. realtime.status on the integration is
active, degraded or inactive; if the provider refused the subscription change, it shows
degraded or inactive until a scheduled check repairs it within a few hours.