StabisoDocs

API reference

Imports

Import customers, vendors, accounts, opening balances and open items from CSV or Excel: dry run, all-or-nothing commit, templates.

Operation Summary Scope Idempotency-Key
POST /v1/entities/{entity}/imports Import customers, vendors, accounts, balances or open items draft required
POST /v1/entities/{entity}/imports/salesforce Import a Salesforce export draft required
GET /v1/entities/{entity}/imports/templates/{type} Download an import template read

POST /v1/entities/{entity}/imports

Import customers, vendors, accounts, balances or open items

Bring a customer's existing books into the ledger from a CSV or Excel file.

Start with a dry run (mode=dry_run, the default): it runs the same code as the import in a savepoint, rolls it back, and returns every problem of the file at once, each with its line (row of the file), column and message, plus a summary of what a commit would create. Fix the file, repeat, then send it again with mode=commit and an Idempotency-Key. A commit is all or nothing: it applies every row in one transaction, or, if any row has a problem, answers 422 with the same list of problems and changes nothing. A retry with the same key and file returns the first answer.

The six types go through the same services as the API does, so every control applies:

  • customers, vendors: created like POST /v1/entities/{entity}/parties. A customer is active; a person's new vendor is a draft with an activation request, and a second person must approve it (GET /v1/approvals) before its bills can be posted or paid, unless the organization's approval policy allows activate_party for vendors.
  • accounts: added like POST /v1/entities/{entity}/accounts (needs the admin scope and the policy's manage_accounts).
  • opening_balances: the trial balance of the old books, one balanced opening entry per entity dated as_of (source opening), posted through the approval policy. Rows for the receivables and payables control accounts are not posted (their balances are the open items); the difference is booked to the account with the role opening_balance_clearing. A second opening entry is refused until the first is reversed: to correct an opening entry, reverse it (POST /v1/journal-entries/{id}/reverse; a second person approves the reversal), then import the balances again.
  • open_receivables, open_payables: every open invoice, bill or credit note becomes a posted document with its original number, dates and open gross amount, booked against the clearing account (receivables or payables control account against clearing), so nothing is counted twice. The customer or vendor must exist and be active. Once the balances and both lists agree, the clearing account is zero (summary.clearing_balance).

An fx_rate more than 10% away from the reference rate (ECB) of the opening date is a problem of its row unless confirm_rates is true (see confirm_rates).

Agent keys are refused (403). Refused: 404 for an unknown entity, 422 for an unreadable file, a file over 5 MB or 2,000 rows, a missing as_of, and (on commit) any problem in the rows; 409 when the Idempotency-Key was used with another file. The commit is an audit event (import.commit) with the file's SHA-256 next to the audit events of everything it created.

Required scope draft (admin implies every scope)
Idempotency-Key required
Operation id import_file_v1_entities__entity__imports_post

Parameters

In Name Type Required Description
path entity string yes Entity code, e.g. CH01 (GET /v1/entities).

Request body

multipart/form-data (required): Body_import_file_v1_entities__entity__imports_post

Field Type Required Description
file string yes The file (multipart form field file): CSV (UTF-8; ,, ; or tab delimited) or Excel .xlsx, whose first row names the columns of the template (GET /v1/entities/{entity}/imports/templates/{type}); at most 5 MB and 2,000 rows.
type "accounts" | "customers" | "open_payables" | "open_receivables" | "opening_balances" | "vendors" yes What the rows are: customers, vendors, accounts, opening_balances (the trial balance of the old books), open_receivables or open_payables (the invoices and bills still open).
mode "commit" | "dry_run" dry_run (the default) checks everything and saves nothing; commit applies the whole file or nothing and needs an Idempotency-Key. (default "dry_run")
as_of string (date) or null The opening date, YYYY-MM-DD: the day the new books start and the opening entries are dated (the balances are those at the end of the day before). Required for opening_balances, open_receivables and open_payables, whose period must be open; ignored by the other types.
confirm_rates boolean Use the fx_rate values of the file even where one differs by more than 10% from the reference rate (ECB) of the opening date. Without it such a row is a problem (rates_to_confirm counts them); with it the row is imported and listed as a warning. (default false)

Example request

curl -X POST "$ERP_URL/v1/entities/CH01/imports" \
  -H "Authorization: Bearer $ERP_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -F "file=@customers.csv" \
  -F "type=accounts"

Responses

Status Meaning Body
200 OK DataImportResult
401 Missing or invalid API key (unauthorized). ErrorEnvelope
403 The caller lacks the required scope (missing_right, with scope), the key belongs to no person where a person is needed (forbidden), or the approval policy denies the action (policy_denied, with action and rule_id). ErrorEnvelope
404 The resource does not exist, or belongs to another organization (not_found). ErrorEnvelope
409 The request conflicts with the data: an Idempotency-Key reused with a different request, a closed period, or an integrity rule of the database (conflict, integrity_violation). ErrorEnvelope
422 Validation failed: the error envelope (validation_failed). For a malformed body or parameter its message is in sentences ("Line 2: the quantity must be above zero") and fields lists each error with pydantic's loc, type and msg beside its sentence (message) and its parts, to word it in another language: items (name and number from 1: line 2), field (its name as sent) and the rule it breaks with its limit, count and choices (none for a validator's own sentence); FastAPI's own detail list stays beside the envelope. HTTPValidationError

Example response (shape)

{
  "type": "accounts",
  "mode": "commit",
  "entity": "string",
  "format": "csv",
  "filename": "string",
  "sha256": "string",
  "as_of": "2026-09-21",
  "rows": 1,
  "ok": true,
  "committed": true,
  "errors": [
    {
      "line": 1,
      "column": "string",
      "message": "string"
    }
  ],
  "warnings": [
    {
      "line": 1,
      "column": "string",
      "message": "string"
    }
  ],
  "summary": {
    "created": 1,
    "waiting_for_activation": 1,
    "posted": 1,
    "waiting_for_approval": 1,
    "total_debit": "string",
    "total_credit": "string",
    "clearing_difference": "string",
    "amounts_by_currency": {},
    "clearing_balance": "string",
    "entry": {
      "entry_id": null,
      "status": null,
      "number": null,
      "approval_id": null
    }
  },
  "rates_to_confirm": 0
}

POST /v1/entities/{entity}/imports/salesforce

Import a Salesforce export

Bring a company's Salesforce data over, one object (one file) at a time: its users (linked to the people here by email), accounts (customers), products, price books and prices, opportunities (deals), and the tasks, events and notes about them.

Import the files in the order of object: each links to those before it (a deal to its account, a task to its deal, a price to its product and price book). Start with a dry run (the default): it returns every problem with its line and the file's column, the mapping of our fields to the file's columns (by Salesforce API name or report label), and a summary of what a commit would create. Name other columns in mapping where the file calls a field differently (a custom field), then send the file with mode=commit and an Idempotency-Key. A commit is all or nothing.

Re-imports create nothing twice: every record is kept with its Salesforce id, so the same file or a newer export imports only what is new (summary.already_imported counts the rest). An account whose code or name is already a customer here is linked to it, not doubled. Rows deleted in Salesforce (IsDeleted) are skipped.

Every record goes through the same controls as the API: customers under the approval policy, VAT numbers checked (one that fails is kept and marked unverified), products on the default revenue account. Agent keys are refused (403). Refused: 404 for an unknown entity, 422 for an unreadable file, a file over the limits, a bad mapping, and (on commit) any problem in the rows; 409 when the Idempotency-Key was used with another file. The commit is an audit event (import.salesforce) with the file's SHA-256.

Required scope draft (admin implies every scope)
Idempotency-Key required
Operation id import_salesforce_v1_entities__entity__imports_salesforce_post

Parameters

In Name Type Required Description
path entity string yes Entity code, e.g. CH01 (GET /v1/entities).

Request body

multipart/form-data (required): Body_import_salesforce_v1_entities__entity__imports_salesforce_post

Field Type Required Description
file string yes The export (multipart form field file): a CSV as Salesforce writes it (the weekly Data Export, Data Loader, Workbench or a report) or an Excel .xlsx; at most 20 MB, 10,000 rows and 800 columns.
object "accounts" | "contacts" | "events" | "notes" | "opportunities" | "price_book_entries" | "price_books" | "products" | "tasks" | "users" yes What the file holds, imported in this order: users (User), accounts (Account), products (Product2), price_books (Pricebook2), price_book_entries (PricebookEntry), opportunities (Opportunity), tasks (Task), events (Event), notes (Note).
mode "commit" | "dry_run" dry_run (the default) checks everything and saves nothing; commit applies the whole file or nothing and needs an Idempotency-Key. (default "dry_run")
mapping string or null Optional JSON object: our field -> the file's column to read it from, or null to leave it empty. Fields it does not name are found by their Salesforce names (the mapping of a dry run shows what was found).

Example request

curl -X POST "$ERP_URL/v1/entities/CH01/imports/salesforce" \
  -H "Authorization: Bearer $ERP_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -F "file=@statement.xml" \
  -F "object=accounts"

Responses

Status Meaning Body
200 OK SalesforceImportResult
401 Missing or invalid API key (unauthorized). ErrorEnvelope
403 The caller lacks the required scope (missing_right, with scope), the key belongs to no person where a person is needed (forbidden), or the approval policy denies the action (policy_denied, with action and rule_id). ErrorEnvelope
404 The resource does not exist, or belongs to another organization (not_found). ErrorEnvelope
409 The request conflicts with the data: an Idempotency-Key reused with a different request, a closed period, or an integrity rule of the database (conflict, integrity_violation). ErrorEnvelope
422 Validation failed: the error envelope (validation_failed). For a malformed body or parameter its message is in sentences ("Line 2: the quantity must be above zero") and fields lists each error with pydantic's loc, type and msg beside its sentence (message) and its parts, to word it in another language: items (name and number from 1: line 2), field (its name as sent) and the rule it breaks with its limit, count and choices (none for a validator's own sentence); FastAPI's own detail list stays beside the envelope. HTTPValidationError

Example response (shape)

{
  "object": "accounts",
  "mode": "commit",
  "entity": "string",
  "format": "csv",
  "filename": "string",
  "sha256": "string",
  "rows": 1,
  "ok": true,
  "committed": true,
  "columns": [
    "string"
  ],
  "mapping": [
    {
      "field": "string",
      "column": "string",
      "required": true,
      "description": "string",
      "label": "string"
    }
  ],
  "unused_columns": [
    "string"
  ],
  "errors": [
    {
      "line": 1,
      "column": "string",
      "message": "string"
    }
  ],
  "warnings": [
    {
      "line": 1,
      "column": "string",
      "message": "string"
    }
  ],
  "summary": {
    "created": 1,
    "already_imported": 1,
    "skipped": 1,
    "records": {}
  }
}

GET /v1/entities/{entity}/imports/templates/{type}

Download an import template

The columns of an import as a file you can fill in: csv or xlsx. The first row names the columns (the onboarding workflow lists them); amounts are plain decimals with a dot, dates YYYY-MM-DD, yes/no columns take yes or no. Every cell of the template is text a spreadsheet cannot run as a formula. The entity must be the caller's (404 otherwise).

Required scope read (admin implies every scope)
Operation id download_template_v1_entities__entity__imports_templates__type__get

Parameters

In Name Type Required Description
path entity string yes Entity code, e.g. CH01 (GET /v1/entities).
path type "accounts" | "customers" | "open_payables" | "open_receivables" | "opening_balances" | "vendors" yes The kind of import: customers, vendors, accounts, opening_balances, open_receivables or open_payables.
query format "csv" | "xlsx" csv (UTF-8 with a byte order mark, comma delimited) or xlsx. (default "csv")
query examples boolean Include two example rows (default); false gives the header row only. Delete the examples before importing: they are rows like any other. (default true)

Example request

curl "$ERP_URL/v1/entities/CH01/imports/templates/vendors" \
  -H "Authorization: Bearer $ERP_KEY"

Responses

Status Meaning Body
200 The template file: a header row with the columns, and two example rows unless examples=false. text/csv, application/vnd.openxmlformats-officedocument.spreadsheetml.sheet
401 Missing or invalid API key (unauthorized). ErrorEnvelope
403 The caller lacks the required scope (missing_right, with scope), the key belongs to no person where a person is needed (forbidden), or the approval policy denies the action (policy_denied, with action and rule_id). ErrorEnvelope
404 The resource does not exist, or belongs to another organization (not_found). ErrorEnvelope
422 Validation failed: the error envelope (validation_failed). For a malformed body or parameter its message is in sentences ("Line 2: the quantity must be above zero") and fields lists each error with pydantic's loc, type and msg beside its sentence (message) and its parts, to word it in another language: items (name and number from 1: line 2), field (its name as sent) and the rule it breaks with its limit, count and choices (none for a validator's own sentence); FastAPI's own detail list stays beside the envelope. HTTPValidationError

Schemas

Body_import_file_v1_entities__entity__imports_post

Field Type Required Description
file string yes The file (multipart form field file): CSV (UTF-8; ,, ; or tab delimited) or Excel .xlsx, whose first row names the columns of the template (GET /v1/entities/{entity}/imports/templates/{type}); at most 5 MB and 2,000 rows.
type "accounts" | "customers" | "open_payables" | "open_receivables" | "opening_balances" | "vendors" yes What the rows are: customers, vendors, accounts, opening_balances (the trial balance of the old books), open_receivables or open_payables (the invoices and bills still open).
mode "commit" | "dry_run" dry_run (the default) checks everything and saves nothing; commit applies the whole file or nothing and needs an Idempotency-Key. (default "dry_run")
as_of string (date) or null The opening date, YYYY-MM-DD: the day the new books start and the opening entries are dated (the balances are those at the end of the day before). Required for opening_balances, open_receivables and open_payables, whose period must be open; ignored by the other types.
confirm_rates boolean Use the fx_rate values of the file even where one differs by more than 10% from the reference rate (ECB) of the opening date. Without it such a row is a problem (rates_to_confirm counts them); with it the row is imported and listed as a warning. (default false)

Body_import_salesforce_v1_entities__entity__imports_salesforce_post

Field Type Required Description
file string yes The export (multipart form field file): a CSV as Salesforce writes it (the weekly Data Export, Data Loader, Workbench or a report) or an Excel .xlsx; at most 20 MB, 10,000 rows and 800 columns.
object "accounts" | "contacts" | "events" | "notes" | "opportunities" | "price_book_entries" | "price_books" | "products" | "tasks" | "users" yes What the file holds, imported in this order: users (User), accounts (Account), products (Product2), price_books (Pricebook2), price_book_entries (PricebookEntry), opportunities (Opportunity), tasks (Task), events (Event), notes (Note).
mode "commit" | "dry_run" dry_run (the default) checks everything and saves nothing; commit applies the whole file or nothing and needs an Idempotency-Key. (default "dry_run")
mapping string or null Optional JSON object: our field -> the file's column to read it from, or null to leave it empty. Fields it does not name are found by their Salesforce names (the mapping of a dry run shows what was found).

DataImportResult

Field Type Required Description
type "accounts" | "customers" | "open_payables" | "open_receivables" | "opening_balances" | "vendors" yes The kind of import.
mode "commit" | "dry_run" yes dry_run (nothing is saved) or commit (all or nothing).
entity string yes Code of the entity the import is for.
format "csv" | "xlsx" yes csv or xlsx, as detected from the file.
filename string yes The uploaded file's name.
sha256 string yes SHA-256 of the file, kept in the audit trail.
as_of string (date) or null yes The opening date of an opening_balances or open item import; null otherwise.
rows integer yes Data rows in the file (empty rows are skipped).
ok boolean yes True when the file has no error: a dry run says a commit would succeed; a commit has succeeded.
committed boolean yes True only for a commit that changed the books; always false for a dry run.
errors array of ImportIssue yes Every row-level problem found (all of them, not the first): a dry run lists them with 200, a commit refuses with 422 and the same list.
warnings array of ImportIssue yes Things worth knowing that do not stop the import, e.g. an ignored column or a trial balance row for a control account.
summary ImportSummary yes What was (or would be) created.
rates_to_confirm integer Rows whose fx_rate differs by more than 10% from the reference rate of the opening date: problems unless the import was sent with confirm_rates, warnings with it. (default 0)

FieldMapping

Which column of the file one of our fields is read from.

Field Type Required Description
field string yes Our field, e.g. name or country.
column string or null yes The file's column it is read from (as the header names it, compared without case, spaces and hyphens); null when the file has none and the field is left empty.
required boolean yes True when a row cannot be imported without it.
description string yes What the field is, and the Salesforce field it is.
label string or null The name of a field of the organization's own (custom.<key>); null for the import's fields.

ImportEntry

The opening entry an opening_balances import posted or queued.

Field Type Required Description
entry_id string (uuid) yes Id (uuid) of the journal entry.
status string yes posted, or pending_approval when the approval policy sends it to a second person.
number string or null yes The entry number once posted; null before.
approval_id string (uuid) or null yes The approval request (GET /v1/approvals) while it waits; null otherwise.

ImportIssue

One problem found in the file, or one thing worth knowing.

Field Type Required Description
line integer or null yes The row in the file (the header is row 1); null for a problem with the file as a whole or with the entry built from all rows.
column string or null yes The column of the template the problem is in; null when it is about the row or the file as a whole.
message string yes What is wrong, and how to fix it.

ImportSummary

What the import created (or, in a dry run, would create).

Field Type Required Description
created integer yes Records created: customers, vendors, accounts, or documents (open items); 1 for the opening entry.
waiting_for_activation integer or null Vendors (and customers, if the approval policy says so) created as drafts that a second person must activate (GET /v1/approvals) before their documents can be posted or paid; null for other imports.
posted integer or null Open items posted (their opening entries are in the ledger); null for other imports.
waiting_for_approval integer or null Entries the approval policy sent to a second person instead of posting; null for other imports.
total_debit string or null opening_balances: the debits of the file in the entity's currency.
total_credit string or null opening_balances: the credits of the file in the entity's currency.
clearing_difference string or null opening_balances: debits less credits of the file, which the entry books to the opening balance clearing account (the receivables and payables of the trial balance are imported as open items, so they are not in the file's entry); 0 when the file balances by itself.
amounts_by_currency map of string or null Open items: the total imported per currency (credit notes count negative).
clearing_balance string or null The balance of the entity's opening balance clearing account after the import (debits less credits), in its functional currency. After the opening balances and both open item lists are imported it is 0 when they agree.
entry ImportEntry or null opening_balances: the opening entry; null otherwise.

SalesforceImportResult

Field Type Required Description
object "accounts" | "contacts" | "events" | "notes" | "opportunities" | "price_book_entries" | "price_books" | "products" | "tasks" | "users" yes What the file holds.
mode "commit" | "dry_run" yes dry_run (nothing is saved) or commit (all or nothing).
entity string yes Code of the entity the import is for.
format "csv" | "xlsx" yes csv or xlsx, as detected from the file.
filename string yes The uploaded file's name.
sha256 string yes SHA-256 of the file, kept with what it created.
rows integer yes Data rows in the file (empty rows are skipped).
ok boolean yes True when the file has no error: a dry run says a commit would succeed; a commit has succeeded.
committed boolean yes True only for a commit that saved the records.
columns array of string yes The file's columns, as the header names them.
mapping array of FieldMapping yes Each of our fields and the column it is read from: found by its Salesforce name (API name or label), or as mapping named it.
unused_columns array of string yes Columns of the file no field is read from (Salesforce exports every field; most are not needed).
errors array of ImportIssue yes Every problem found (all of them): a dry run lists them with 200, a commit refuses with 422 and the same list.
warnings array of ImportIssue yes Things worth knowing that do not stop the import: a row skipped, a value shortened, a code made up because the file has none.
summary SalesforceSummary yes What was (or would be) created.

SalesforceSummary

What the import created (or, in a dry run, would create).

Field Type Required Description
created integer yes Rows imported now: each became a record.
already_imported integer yes Rows whose Salesforce id an earlier import already brought over: left as they are, nothing is created twice.
skipped integer yes Rows not imported, each with a note saying why (deleted in Salesforce, about a record that was not imported, ...).
records map of integer yes What was created, by kind: customers, deals, tasks, notes, products, prices, price_lists, people_linked, ...
Stabiso, the Enterprise System of Record · generated from the code and its guides · release 20b7dd5b0c4d