{"openapi":"3.1.0","info":{"title":"Fibady Public API","version":"1.0.0","description":"## Overview\n\nThe public API of Fibady loads the accounting, invoicing and banking data of the companies of a Fibady account in batches, and reads back what was loaded. This is version 1: the base URL is `https://public-api.fibady.com/v1`, and every path is under `/v1`.\n\nRequests and responses are JSON in UTF-8, except the token request, which is a form. The error messages of the resource operations follow `Accept-Language` (English by default), and every response emitted by the API carries `X-Request-Id` (see Errors and Retries and rate limits).\n\nStart with the Quickstart. The reference of every operation follows this guide, grouped by resource.\n\n## Quickstart\n\nSeven steps in POSIX `sh` (they also work in bash, dash and zsh), with `curl` and `jq`. The client secret never goes on the command line: it is typed without echo and handed to `curl` on its standard input (`curl -K -`), so it shows up neither in `ps` nor in the shell history.\n\nPaste each block whole. In the block that reads the secret, the read is its last line: a shell without bracketed paste cannot take a line pasted after it as the secret.\n\n**1. Create a client.** In the Fibady app, go to **Settings → Organization → API and integrations** (`/settings/organization/api-integrations`), create an API client limited to a test company and choose its scopes; this guide needs `companies:read`, `accounting:read`, `accounting:write` and `ingestion:read`. Copy its client id (`fapi_…`) and its secret. The API must also be the source of the test company's journal entries or invoices, and neither of them may come from an ERP: otherwise its accounts cannot be written through the API (`catalogs`, in step 3, says so). The app does not offer that choice yet: once the client exists, ask Fibady to set the API as that source.\n\n**2. Get an access token.** Replace `fapi_…` with your client id and paste the block. It defines `fibady_token`, which requests a token with HTTP Basic authentication (`client_secret_basic`) and caches it until `exp − 60 s`, and `fibady`, which calls the API with it and prints the response body. On an error status, `fibady` prints the status and the body on standard error and fails; after a 401 it also drops the cached token. The last line of the block asks for the client secret and requests the first token.\n\n```sh\nFIBADY_CLIENT_ID='fapi_…'\nFIBADY_API='https://public-api.fibady.com'\nFIBADY_TOKEN_CACHE=\"${XDG_CACHE_HOME:-$HOME/.cache}/fibady-public-api-token-$(printf '%s|%s' \"$FIBADY_API\" \"$FIBADY_CLIENT_ID\" | cksum | cut -d' ' -f1).json\"\n\nfibady_token() {\n  now=$(date +%s)\n  exp=$(jq -r '.exp // 0' \"$FIBADY_TOKEN_CACHE\" 2>/dev/null || echo 0)\n  if [ \"$exp\" -gt $((now + 60)) ]; then\n    jq -r '.access_token' \"$FIBADY_TOKEN_CACHE\"; return\n  fi\n  resp=$(printf 'user = \"%s:%s\"\\n' \"$FIBADY_CLIENT_ID\" \"$FIBADY_CLIENT_SECRET\" \\\n    | curl -sS -K - -d grant_type=client_credentials \"$FIBADY_API/v1/oauth/token\")\n  token=$(printf '%s' \"$resp\" | jq -r '.access_token // empty')\n  [ -n \"$token\" ] || { printf '%s\\n' \"$resp\" >&2; return 1; }\n  ( umask 077; mkdir -p \"$(dirname \"$FIBADY_TOKEN_CACHE\")\"\n    printf '%s' \"$resp\" | jq --argjson now \"$now\" '{access_token, exp: ($now + .expires_in)}' \\\n      > \"$FIBADY_TOKEN_CACHE\" )\n  printf '%s\\n' \"$token\"\n}\n\nfibady() {\n  token=$(fibady_token) || return 1\n  method=$1; route=$2; shift 2\n  out=$(printf 'header = \"Authorization: Bearer %s\"\\n' \"$token\" \\\n    | curl -sS -K - -X \"$method\" -w '\\n%{http_code}' \"$@\" \"$FIBADY_API$route\") || return 1\n  code=$(printf '%s\\n' \"$out\" | tail -n 1)\n  body=$(printf '%s\\n' \"$out\" | sed '$d')\n  [ \"$code\" != 401 ] || rm -f \"$FIBADY_TOKEN_CACHE\"\n  [ \"$code\" -lt 400 ] || { printf 'HTTP %s: %s\\n' \"$code\" \"$body\" >&2; return 1; }\n  printf '%s\\n' \"$body\"\n}\n\nprintf 'FIBADY_CLIENT_SECRET: '; stty -echo; IFS= read -r FIBADY_CLIENT_SECRET; stty echo; printf '\\n'; fibady_token >/dev/null && echo 'Token ready.'\n```\n\n**3. Find the company.** `GET /v1/companies` lists the companies the client can access; `id` is the `companyId` of every other path, and `catalogs.accounts.writable` must be `true`. If the client can access several companies, set `FIBADY_COMPANY` to the `id` of the test one.\n\n```sh\nfibady GET /v1/companies | jq '.items[] | {id, name, accounts: .catalogs.accounts}'\nFIBADY_COMPANY=$(fibady GET /v1/companies | jq -r '.items[0].id // empty'); echo \"$FIBADY_COMPANY\"\n```\n\n**4. Validate a batch.** With `dryRun=true` the batch is validated exactly like a real write and nothing is written: each account comes back `valid`, with its warnings, or the response is a 422 `validation_failed` whose `details` say what to fix. Accounts are identified by `code`, and a `code` that already exists in the company, whatever loaded it, is adopted: the account takes the `name` sent and from then on belongs to the API. Run the quickstart on a test company only.\n\n```sh\nFIBADY_ACCOUNTS='{\"items\": [\n  {\"code\": \"7000001\", \"name\": \"Sales of goods\"},\n  {\"code\": \"5720001\", \"name\": \"Main bank account\"}\n]}'\nfibady POST \"/v1/companies/$FIBADY_COMPANY/accounts/batch?dryRun=true\" \\\n  -H 'Content-Type: application/json' --data-binary \"$FIBADY_ACCOUNTS\" | jq .\n```\n\n**5. Load it.** The same batch, without `dryRun`, and with an `Idempotency-Key`. The response is the run: `runId`, `status` (`succeeded`, or `partial` when some record is `unprojectable`), `counts` and one `results` entry per account. If the response is lost, or is a 5xx, paste the block again: it keeps `FIBADY_KEY`, and with the same key a write that already finished returns its stored response, with `Idempotent-Replayed: true`. Run `unset FIBADY_KEY` before loading a different batch.\n\n```sh\nFIBADY_KEY=${FIBADY_KEY:-accounts-$(date +%s)}\nFIBADY_RESULT=$(fibady POST \"/v1/companies/$FIBADY_COMPANY/accounts/batch\" \\\n  -H 'Content-Type: application/json' -H \"Idempotency-Key: $FIBADY_KEY\" \\\n  --data-binary \"$FIBADY_ACCOUNTS\")\nprintf '%s\\n' \"$FIBADY_RESULT\" | jq .\nFIBADY_RUN=$(printf '%s' \"$FIBADY_RESULT\" | jq -r '.runId // empty'); echo \"$FIBADY_RUN\"\n```\n\n**6. Follow the run.** Poll `GET /v1/companies/{companyId}/runs/{runId}` with backoff until `status` is `succeeded`, `partial` or `failed`; any other value means poll again. If a call fails, `fibady` prints why and the loop stops: paste the block again to resume.\n\n```sh\ndelay=1; run=\nwhile [ -n \"$FIBADY_RUN\" ]; do\n  run=$(fibady GET \"/v1/companies/$FIBADY_COMPANY/runs/$FIBADY_RUN\") || break\n  run_status=$(printf '%s' \"$run\" | jq -r '.status')\n  case \"$run_status\" in succeeded|partial|failed) break ;; esac\n  sleep \"$delay\"; [ \"$delay\" -ge 30 ] || delay=$((delay * 2))\ndone\nprintf '%s\\n' \"$run\" | jq '{status, counts, reportsVisibility, postProcessing}'\n```\n\n**7. Check the reports and read the data back.** `reportsVisibility` says whether what this batch changed is already in the reports (`visible`) or not yet (`pending`, which may last: see Runs and report visibility). Accounts have no `GET` by identifier, so read them back with the listing `GET /v1/companies/{companyId}/accounts`, which returns the whole chart of accounts sorted by `code`: the block follows `nextCursor` to the last page. The other entities have `GET /v1/companies/{companyId}/{entity}/{externalId}`, which says whether one record is `projected` in Fibady or still `pending`.\n\n```sh\npage=$(fibady GET \"/v1/companies/$FIBADY_COMPANY/accounts\")\nwhile [ -n \"$page\" ]; do\n  printf '%s\\n' \"$page\" | jq '.items[] | select(.code == \"7000001\" or .code == \"5720001\")'\n  cursor=$(printf '%s' \"$page\" | jq -r '.nextCursor // empty')\n  [ -n \"$cursor\" ] || break\n  page=$(fibady GET \"/v1/companies/$FIBADY_COMPANY/accounts\" -G --data-urlencode \"cursor=$cursor\")\ndone\n```\n\n## Authentication\n\nOAuth 2.0 client credentials (RFC 6749 §4.4). Request an access token with `POST /v1/oauth/token` on the same host you call, authenticating the client with `client_secret_basic` or `client_secret_post`, and send it as `Authorization: Bearer <token>` on every other request.\n\nCache the token and reuse it until `exp − 60 s`, where `exp` is the time you received it plus `expires_in` (600 seconds); then request a new one. Do not request a token per call: a client may request at most 60 tokens per hour.\n\n<!-- ReDoc-Inject: <security-definitions> -->\n\nErrors of the token endpoint use the RFC 6749 body `{ \"error\": \"<code>\", \"error_description\": \"<text>\" }`: branch on `error`.\n\n| Group | `error` | Status | Retryable | Meaning |\n|---|---|---|---|---|\n| Validation | `invalid_request` | 400 | No | Malformed token request: wrong `Content-Type`, body too large, missing `grant_type`, both client authentication methods, or a `client_id` that does not match. |\n| Validation | `unsupported_grant_type` | 400 | No | `grant_type` is not `client_credentials`. |\n| Credentials and tokens | `invalid_client` | 401 | No | Unknown, inactive or revoked client, wrong secret, or missing or malformed client authentication. |\n| Access | `origin_forbidden` | 403 | No | The request did not come through the public API endpoint. |\n| Rate limits and quota | `rate_limited` | 429 | Yes | The client exceeded its tokens per hour. Wait at least `Retry-After`, and cache the token instead of requesting one per call. |\n| Server | `server_error` | 500 | Yes | Unexpected failure. Retry with backoff. |\n| Server | `temporarily_unavailable` | 503 | Yes | The public API is not available for the client's account, or a dependency failed. Wait at least `Retry-After`; if it persists for more than 5 attempts in a row, it means the same as `public_api_disabled`. |\n\n## Sandbox and environments\n\nThere is a single server, production, and no separate sandbox. The sandbox of version 1 is `dryRun=true` on a test company: create a company for testing in your Fibady account and an API client limited to it.\n\nA `dryRun` runs the same checks as the real write, with the same warnings, and writes nothing: no run, no `runId` and no daily quota, although it counts for the rate limit. The `Idempotency-Key` is ignored with `dryRun=true`.\n\n## Load order and currency\n\nLoad the records that others refer to first. A record that refers to one that is not loaded yet is rejected with a 422:\n\n- accounts → analytic values → partners → journal entries and invoices → invoice payments;\n- bank accounts → bank movements.\n\nJournal entry amounts are always in the company's currency. Invoices and bank movements may be in another currency: load the `closing` exchange rates in the Fibady app before them. Without a rate, the record is loaded with the warning `fx_rate_missing`, and its amount counts unconverted in the reports. Once the rate is loaded, the record is converted automatically only if it is dated in the month the rate is loaded or later, or in the three months before it, or if the Fibady account uses its own exchange rates: an older record may stay unconverted, and sending it again unchanged does not convert it. So load the rates before a historical load. Loading exchange rates through the API (`/v1/exchange-rates`) is planned for version 1.1.\n\n## Idempotency\n\nSend an `Idempotency-Key` header on every write. The same key with the same body returns the stored response instead of writing again, with `Idempotent-Replayed: true`. The same key with a different body, or on a different operation, is a 422 `idempotency_key_reused`; while the first request is still running, a 409 `idempotency_key_in_progress` (wait at least `Retry-After` and send it again with the same key).\n\nA batch is not atomic. Its validation is: a 422 writes nothing. Its load is not: each record is applied on its own and identified by its `externalId` (by its `code`, for accounts), and a record that did not change comes back `unchanged`. So sending the same batch again after a 5xx or a timeout is safe and converges, with or without a key.\n\nA deletion is idempotent too: repeating it without a key returns `not_found` for the records already deleted.\n\n## Runs and report visibility\n\nEvery real write creates a run, identified by `runId`. Its `status` goes from `in_progress` to `succeeded`, `partial` (some records are `unprojectable`) or `failed`. Poll `GET /v1/companies/{companyId}/runs/{runId}` with backoff until `status` is one of those three; any other value means poll again.\n\n`reportsVisibility: visible` means that what THIS batch changed is already in the reports; an `unchanged` record changed nothing in this batch, so its visibility is that of the run that wrote it. To retry a batch, send it again with the same `Idempotency-Key`: the retry keeps what the previous attempt dispatched.\n\n`pending` may last. The reports of a large company are refreshed as a whole, not by month, and a run of invoices or bank movements may stay `pending` for a long time. That is not a failure of the load: do not wait for `visible` to consider a batch loaded. `postProcessing` is `done` once the run is in the import history.\n\n## Retries and rate limits\n\nA 429 or a 503 from the API carries `Retry-After` (seconds) and `X-Request-Id`: wait at least `Retry-After` before retrying, and back off further if it repeats. A 429 without `Retry-After` (the gateway throttle does not send it): wait at least 1 second, with exponential backoff.\n\nA 409 `batch_in_progress` (the company's load lock is taken, normally by another batch of the company) or `idempotency_key_in_progress` (a request with the same `Idempotency-Key` is still running) is retryable too: that response wrote nothing and carries `Retry-After`. Wait at least `Retry-After` and send the same request again, with the same `Idempotency-Key` if it had one.\n\nSome errors do not come from the API and are outside this contract: a 429 or a 5xx without `X-Request-Id` and without the error bodies below (edge rate limit, gateway throttling, exhausted concurrency or the 30-second gateway timeout). Retry them with exponential backoff and jitter.\n\nA 401 `api_token_invalid` means the token expired or was revoked: request a new one and retry once.\n\nThe only retryable 4xx responses are a 429, a 409 `batch_in_progress` or `idempotency_key_in_progress`, and a 401 `api_token_invalid` (once, with a new token). Every other 4xx is final, including a 409 with any other code: fix the request before sending it again.\n\n**Exception: a 503 `public_api_disabled`.** The public API is not enabled for the account (invitation-only preview) or has been switched off in an emergency. Do not retry immediately or in a loop at the pace of `Retry-After`, even though the response carries it: back off exponentially, with at most 5 minutes between attempts, or stop and contact Fibady quoting `X-Request-Id`. A 503 `temporarily_unavailable` from the token endpoint that persists for more than 5 attempts in a row means the same.\n\n## Errors\n\nEvery response emitted by the API carries `X-Request-Id`; quote it when you contact Fibady through your usual channel. Resource errors use `{ \"error\": \"<message>\", \"code\": \"<code>\" }`: branch on `code`, never on `error`, whose text follows `Accept-Language` (English by default). A 422 `validation_failed` adds `details`, the list of problems; some 409 add a `details` object whose keys depend on the `code`. The token endpoint uses the RFC 6749 body instead (see Authentication).\n\nThe codes of the resource operations, by group. Retryable means that sending the same request again, after `Retry-After` or with backoff, may succeed; otherwise fix the request, the credential or the account first. `details[].code` of a 422 and `results[].code` of a record are not error codes: see Forward compatibility.\n\n### Credentials and tokens\n\n| Code | Status | Retryable | Meaning |\n|---|---|---|---|\n| `api_token_missing` | 401 | No | No `Authorization: Bearer` header. |\n| `api_token_invalid` | 401 | Yes | Unknown, expired or revoked access token, or an inactive client. Request a new token and retry once. |\n\n### Access\n\n| Code | Status | Retryable | Meaning |\n|---|---|---|---|\n| `auth_invalid_or_expired_license` | 402 | No | The account's license is not valid. |\n| `api_origin_forbidden` | 403 | No | The request did not come through the public API endpoint. |\n| `api_insufficient_scope` | 403 | No | The client lacks the scope the operation needs. The response carries `WWW-Authenticate`. |\n| `api_company_not_allowed` | 403 | No | The client cannot access the company, whether it exists or not. |\n| `api_company_param_conflict` | 403 | No | A `companyId` in the query or in the body does not match the one in the path: the company is always the one of the path. |\n| `public_api_disabled` | 503 | No | The public API is not enabled for the account (invitation-only preview) or has been switched off in an emergency. Do not retry immediately or in a loop at the pace of `Retry-After`, even though the response carries it: back off exponentially, with at most 5 minutes between attempts, or stop and contact Fibady quoting `X-Request-Id`. A 503 `temporarily_unavailable` from the token endpoint that persists for more than 5 attempts in a row means the same. |\n\n### Validation\n\n| Code | Status | Retryable | Meaning |\n|---|---|---|---|\n| `api_invalid_query` | 400 | No | An unknown, repeated or invalid query parameter. |\n| `api_invalid_json` | 400 | No | The request body is not valid JSON. |\n| `api_resource_not_found` | 404 | No | There is no such record or run for the company. The same response for an id that does not exist, belongs to another company or source, or is malformed. |\n| `api_payload_too_large` | 413 | No | The request body is larger than the maximum size (see Limits). |\n| `api_batch_too_large` | 413 | No | The batch has more items than the operation accepts (see Limits). |\n| `validation_failed` | 422 | No | Some item of the batch is invalid and nothing was written or deleted: `details` lists the problems. |\n| `analytic_dimension_code_reserved` | 409 | No | The code of some dimension of the batch is already taken by a dimension that did not come from the API; `details.dimensions` lists them. Nothing was written. |\n\n### Idempotency\n\n| Code | Status | Retryable | Meaning |\n|---|---|---|---|\n| `idempotency_key_reused` | 422 | No | The `Idempotency-Key` was already used with a different body or operation. |\n| `idempotency_key_in_progress` | 409 | Yes | A request with the same `Idempotency-Key` is still running; this one wrote nothing. Wait at least `Retry-After` and send it again with the same key. |\n| `batch_in_progress` | 409 | Yes | Another batch of the company is running; this one wrote nothing. Wait at least `Retry-After` and send the same request again. |\n\n### Sources\n\n| Code | Status | Retryable | Meaning |\n|---|---|---|---|\n| `source_not_owned` | 409 | No | The company's data for this entity is not fed by the API: `details.reason` is `source_not_assigned` (it has no source) or `source_not_api` (its source is not the API). Nothing was written. |\n| `erp_api_mix_not_supported` | 409 | No | Of the company's journal entries and invoices, one is fed by the API and the other by an ERP connector. Nothing was written. |\n\n### Deletes\n\n| Code | Status | Retryable | Meaning |\n|---|---|---|---|\n| `delete_confirmation_required` | 409 | No | The batch would delete a large share of the company's records and `expectedDeletes` was not sent: send it again with `expectedDeletes`. Nothing was deleted. |\n\n### Rate limits and quota\n\n| Code | Status | Retryable | Meaning |\n|---|---|---|---|\n| `api_rate_limited` | 429 | Yes | Too many requests for the account or the client. Wait at least `Retry-After`. |\n| `api_quota_exceeded` | 429 | Yes | The account's daily write quota is used up. Wait at least `Retry-After`. |\n| `api_concurrent_requests_exceeded` | 429 | Yes | Too many writes of the account in flight. Wait at least `Retry-After`. |\n\n### Server\n\n| Code | Status | Retryable | Meaning |\n|---|---|---|---|\n| `internal_server_error` | 500 | Yes | Unexpected failure. Retry with backoff: a write may be partially applied, and sending it again is safe. |\n| `api_temporarily_unavailable` | 503 | Yes | A dependency failed, or there was not enough time left to start a write. Wait at least `Retry-After`. |\n\n## Forward compatibility\n\nIgnore the properties you do not recognize, in every response, errors included.\n\nEvery enum of a response, except the three of `sources[]` below, may gain new values within `/v1`, but never loses or renames one (the descriptions of the fields call this «closed per version»). When a value you do not know arrives, apply the rule of its class, which the description of each field repeats:\n\n- `results[].code` of a record: Treat an unknown value as `other`.\n- `details[].code` of a 422: Treat an unknown value as a generic validation error: the record was not written.\n- `outcome` of a write or a deletion: Treat an unknown value as an unconfirmed result: check the record before retrying it.\n- `entity` and `operation` of a run: An unknown value is an entity or operation this client does not know: ignore the run.\n- lifecycle (`status`, `reportsVisibility`, `postProcessing` and any `state`): An unknown value is NOT terminal: poll again.\n- `results[].warnings[]`: Treat an unknown value as a generic warning: it does not block the record.\n- `errorClass` of a run: Treat an unknown value as a generic error, the same as `internal`.\n- `catalogs.*.reason` of a company: Show it as is; do not branch on it.\n\nNo value is ever renamed or retired. Exception: `sources[].type`, `sources[].kinds` and `sources[].reason` of a company are fixed, and never gain values within `/v1`.\n\n## Deletes and listings\n\nJournal entries, invoices and bank movements are deleted with `POST …/batch-delete`, by the `externalId` they were written with; the other entities cannot be deleted through the API (the payments of an invoice go away with it). Nothing is deleted by absence: a record you stop sending stays in Fibady.\n\n- Confirm first: run the deletion with `dryRun=true`. When the company has at least 10 live records of the entity written through the API and the batch would delete more than 30% of them, send `expectedDeletes` with the number of items of the batch; without it the response is a 409 `delete_confirmation_required` and nothing is deleted.\n- After a 5xx or a timeout, send the same deletion again BEFORE sending any of those records through `…/batch`: a repeated deletion completes it.\n- The replay of a deletion (the same `Idempotency-Key`) describes what happened then, with `reportsVisibility` and `postProcessing` up to date.\n- Deleting a record dated in a month up to `lastClosedMonth` is not blocked: it carries the warning `date_in_closed_period` and recalculates the budget variance and the forecast of that month.\n- The run of a deletion of invoices or bank movements may stay `pending` even when the data is already recalculated, and so may one of journal entries when the reports of the company are not refreshed by month.\n\nListings of records (`GET` of `…/analytic-values`, `…/partners`, `…/journal-entries`, `…/invoices`, `…/invoice-payments`, `…/bank-accounts` and `…/bank-movements`) return only the records written through the API, one summary per record, in an internal order:\n\n- Paginate with `cursor` until `nextCursor` is `null`: a page may be shorter than `limit`, even empty, with a non-null `nextCursor`.\n- For an incremental sync, pass `updatedSince`. Every page of a pass returns the same `asOf`: start the next pass with `updatedSince` set to it. A record may appear in two passes: deduplicate by `externalId`.\n- Deleted records are not listed, and neither are the payments of a deleted invoice until the invoice and its payments are sent again.\n- `updatedSince` needs a time zone: send `Z`, or encode the `+` of a positive offset as `%2B`.\n\nThe listing of accounts (`GET …/accounts`) is not one of them: it returns every live account of the company's chart of accounts, whatever loaded it (manual entry, a CSV import, an ERP connector or the API), sorted by `code`, without `updatedSince` or `asOf`. Paginate it with `cursor` until `nextCursor` is `null`; a page is shorter than `limit` only when it is the last one.\n\n## External ids\n\n`externalId` is your identifier of a record: unique within a batch and scoped to the company of the path, so the same `externalId` in two companies identifies two records. Percent-encode it in the path of `GET …/{entity}/{externalId}`.\n\nAn `externalId` that contains `/` cannot be read with `GET …/{entity}/{externalId}`, even percent-encoded: the response is a 404. Such a record can still be written, deleted and found in the listing.\n\n## Limits\n\nThese limits may go up within `/v1`, never down:\n\n- Request body: at most 1 MiB (`api_payload_too_large`).\n- Batch size (`api_batch_too_large`): 500 records of accounts, analytic values, partners or bank accounts; 200 journal entries or invoices, with at most 2000 lines in the whole batch; 500 bank movements; 500 invoice payments; 500 items per `batch-delete`.\n- Page size (`limit`, whose default is the maximum): 100 companies; 500 accounts; 500 records in the other listings.\n- Access tokens: at most 60 per client and hour, each valid for 600 seconds.\n\nThe request rate, the daily write quota and the writes in flight of an account are not published as figures: when one is reached, the response is a 429 with `Retry-After` (see Retries and rate limits).\n\n## Versioning and changelog\n\nWithin `/v1` changes are additive only. Response objects may gain new properties: ignore the ones you do not recognize.\n\nA breaking change goes to a new version under `/v2`. The `/v1` operations it replaces announce it with the `Deprecation` and `Sunset` response headers, and both versions run side by side for at least 6 months before those `/v1` operations are retired.\n\nThe changelog lists the versions of this document, newest first; `info.version` is the newest.\n\n### 1.0.0 (2026-10-25)\n\n- First version, in private preview: the API is enabled only for invited accounts.\n- Access tokens (OAuth 2.0 client credentials), companies and runs.\n- Batch writes and reads of accounts, analytic values, journal entries, partners, invoices, invoice payments, bank accounts and bank movements, and batch deletes of journal entries, invoices and bank movements.\n\nNew operations: `issueAccessToken`, `getOpenApiDocument`, `listCompanies`, `getRun`, `listAccounts`, `upsertAccounts`, `listAnalyticValues`, `getAnalyticValue`, `upsertAnalyticValues`, `listJournalEntries`, `getJournalEntry`, `upsertJournalEntries`, `deleteJournalEntries`, `listPartners`, `getPartner`, `upsertPartners`, `listInvoices`, `getInvoice`, `upsertInvoices`, `deleteInvoices`, `listInvoicePayments`, `getInvoicePayment`, `upsertInvoicePayments`, `listBankAccounts`, `getBankAccount`, `upsertBankAccounts`, `listBankMovements`, `getBankMovement`, `upsertBankMovements`, `deleteBankMovements`."},"servers":[{"url":"https://public-api.fibady.com","description":"Production."}],"tags":[{"name":"Access tokens","description":"Every other operation except `GET /v1/openapi.json` needs an access token. Request one with `POST /v1/oauth/token` (OAuth 2.0 client credentials) and send it as `Authorization: Bearer <token>`.\n\nA token lasts 600 seconds and a client may request at most 60 tokens per hour: cache it and reuse it until `exp − 60 s`."},{"name":"Companies","description":"The companies the client can access. The `id` of each one is the `companyId` of every other path.\n\n`catalogs` says whether each catalog could be written through the API when the request was served. It is a snapshot: if the company's sources change afterwards, the response of the batch is the one that counts."},{"name":"Runs","description":"Every real write batch creates a run. Its `status` goes from `in_progress` to `succeeded`, `partial` or `failed`.\n\nPoll `GET /v1/companies/{companyId}/runs/{runId}` with backoff until `status` is terminal (`succeeded`, `partial` or `failed`); any other value, including one this client does not know, means poll again. `reportsVisibility` tells when what the batch changed is in the reports."},{"name":"Accounts","description":"The company's chart of accounts: list it and load it in batches, identified by `code`. Accounts have no `GET` by identifier: read them back with the listing.\n\nA classification hint that is not sent keeps the stored one, and the accounts a batch leaves unclassified get the company's code-prefix rules applied."},{"name":"Analytic values","description":"Analytic values (cost centers, projects and the like) belong to dimensions named by the client and shared by all the companies of the account.\n\n`api:` is the reserved prefix of the dimensions written through the API: in Fibady a dimension created by a batch carries the code `api:<dimension>`, and if a dimension that did not come from the API already has that code, the batch is a 409 `analytic_dimension_code_reserved`."},{"name":"Journal entries","description":"The journal entries of the company, identified by `externalId`. Every upsert replaces the WHOLE entry: its lines are identified by their position in `lines`, and the entry must balance.\n\nIts accounts may come from any source, but its partners and analytic values must have been loaded through the API first (see Load order and currency). `entryType` sets the typology of the whole entry: omitted keeps the stored one, and `null` turns it back into an ordinary entry.\n\nWithin a line and dimension, analytic percentages add up to 100 at most and are never rescaled. `reportsVisibility` turns `visible` when the months the batch touched are in the reports. Entries are deleted with `batch-delete`."},{"name":"Partners","description":"The customers and suppliers of the company, identified by `externalId`. Every upsert writes the whole partner: an optional field that is not sent, or is `null`, is cleared, and so are the currency, the external reference and the invoiced and outstanding totals that Fibady keeps for the partner.\n\nPartners are never deleted through the API; send `active: false` to deactivate one. A partner that was deleted in Fibady is restored when a batch writes it again."},{"name":"Invoices","description":"Issued and received invoices and their refunds, identified by `externalId` and upserted whole: an optional field that is not sent is cleared, and lines are identified by their position. An invoice loaded any other way (an ERP connector, a CSV import, manual entry) is never matched or changed.\n\nThe payment status of an invoice is derived, never accepted: `paid` invoices are paid and every other `status` is unpaid. The payment date comes from its invoice payments.\n\nAn invoice in a currency other than the company's needs a `closing` exchange rate in Fibady; without it, it is loaded with the warning `fx_rate_missing`. `reportsVisibility` may stay `pending` until the reports of the whole company are rebuilt. Invoices are deleted with `batch-delete`, with their lines and their payments."},{"name":"Invoice payments","description":"Each record is one payment applied to one invoice loaded through the API: a payment that settles several invoices is sent as one record per invoice, with the part applied to each one in `dedicatedAmount`. The payment date of a `paid` invoice is the latest `paymentDate` of its payments.\n\nPayments are not deleted through the API: they go away with their invoice, and come back when the invoice and its payments are sent again. The receivables aging and the payment-days series may take up to 5 minutes to show a new payment."},{"name":"Bank accounts","description":"The bank accounts, cash accounts and cards of the company. Once an account exists its `name` never changes: the one sent is ignored.\n\n`openingBalance` and `openingBalanceDate` replace the stored ones when sent and are kept when omitted. Set the opening balance date before the first movement you send."},{"name":"Bank movements","description":"The movements of the bank accounts loaded through the API, identified by `externalId`. There is no `currency` field: a movement is stored in the currency of its account, and keeps the account it was first loaded into.\n\n`amount` carries the direction in its sign: positive is money in, negative is money out. After the batch, the company's bank rules classify the new movements (see `postProcessing`).\n\nA movement of an account in a currency other than the company's needs a `closing` exchange rate in Fibady; without it, it is loaded with the warning `fx_rate_missing`. Movements are deleted with `batch-delete`."},{"name":"OpenAPI document","description":"This document, in OpenAPI 3.1. It needs no credentials: download it to browse the contract offline or to generate a client."}],"x-tagGroups":[{"name":"Getting started","tags":["Access tokens","Companies","Runs"]},{"name":"Accounting","tags":["Accounts","Analytic values","Journal entries"]},{"name":"Invoicing","tags":["Partners","Invoices","Invoice payments"]},{"name":"Banking","tags":["Bank accounts","Bank movements"]},{"name":"Reference","tags":["OpenAPI document"]}],"paths":{"/v1/companies":{"get":{"operationId":"listCompanies","summary":"List companies","description":"The companies of the client's allowlist that still exist in its account, sorted by `id`, with whether each kind of data could be loaded through the API. Deleted companies are skipped, so a page may be shorter than `limit`. Paginate with `cursor` until `nextCursor` is `null`.\n\nEvery response carries `X-Request-Id`, `Cache-Control: no-store` and `Pragma: no-cache`. Only the 429 and the 503 carry `Retry-After`. Error messages follow `Accept-Language` (English by default).","tags":["Companies"],"responses":{"200":{"description":"One page of companies.","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicApiCompanyPage"}}}},"400":{"description":"`api_invalid_query`: an unknown or repeated query parameter, a `limit` that is not an integer from 1 to 100, or a malformed `cursor`.","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseWithCode"}}}},"401":{"description":"`api_token_missing` (no `Authorization: Bearer` header) or `api_token_invalid` (unknown, expired or revoked access credential, or an inactive client). Carries `WWW-Authenticate`. Request a new token.","headers":{"WWW-Authenticate":{"schema":{"type":"string"}},"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseWithCode"}}}},"402":{"description":"`auth_invalid_or_expired_license`: the account's license is not valid.","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseWithCode"}}}},"403":{"description":"`api_origin_forbidden`: the request did not come through the public API endpoint.","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseWithCode"}}}},"429":{"description":"`api_rate_limited`: too many requests for the account or the client. Retry after `Retry-After` seconds.","headers":{"Retry-After":{"$ref":"#/components/headers/RetryAfter"},"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseWithCode"},"examples":{"api_rate_limited":{"$ref":"#/components/examples/PublicApiRateLimited"}}}}},"500":{"description":"`internal_server_error`: unexpected failure. Safe to retry with backoff.","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseWithCode"}}}},"503":{"description":"`public_api_disabled` (the public API is not enabled for the client's account, during the invitation-only preview, or has been switched off) or `api_temporarily_unavailable` (a dependency failed). Retry `api_temporarily_unavailable` after `Retry-After` seconds. Do not retry `public_api_disabled` at that pace: back off exponentially, with at most 5 minutes between attempts, or stop and contact Fibady quoting `X-Request-Id`.","headers":{"Retry-After":{"$ref":"#/components/headers/RetryAfter"},"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseWithCode"}}}}},"security":[{"oauth2":["companies:read"]}],"parameters":[{"name":"limit","in":"query","required":false,"description":"Page size, 1 to 100. Defaults to 100.","schema":{"type":"integer","minimum":1,"maximum":100,"default":100}},{"name":"cursor","in":"query","required":false,"description":"Opaque `nextCursor` from the previous page.","schema":{"type":"string","maxLength":200}}],"x-codeSamples":[{"lang":"Shell","label":"curl","source":": \"${FIBADY_ACCESS_TOKEN:?set it to the access_token of POST /v1/oauth/token, or to the output of fibady_token after the Quickstart}\" &&\nprintf 'header = \"Authorization: Bearer %s\"\\n' \"$FIBADY_ACCESS_TOKEN\" \\\n  | curl -sS -K - 'https://public-api.fibady.com/v1/companies'"}]}},"/v1/companies/{companyId}/accounts":{"get":{"operationId":"listAccounts","summary":"List accounts","description":"The live accounts of the company's chart of accounts, whatever loaded them (manual entry, a CSV import, an ERP connector or the API), so that an integration can map its own account codes before loading journal entries. Deleted accounts are not listed. `code` comes exactly as stored.\n\nAccounts are sorted by `code` with the server's collation; do not rely on where spaces or punctuation sort. Paginate with `cursor` until `nextCursor` is `null`: following the cursors never returns the same `code` twice and never skips an account that kept its code throughout, even if the chart changes in between.\n\nEvery response carries `X-Request-Id`, `Cache-Control: no-store` and `Pragma: no-cache`. Only the 429 and the 503 carry `Retry-After`. Error messages follow `Accept-Language` (English by default).","tags":["Accounts"],"responses":{"200":{"description":"One page of accounts.","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicApiAccountPage"}}}},"400":{"description":"`api_invalid_query`: an unknown or repeated query parameter, a `limit` that is not an integer from 1 to 500, or a malformed `cursor`.","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseWithCode"}}}},"401":{"description":"`api_token_missing` (no `Authorization: Bearer` header) or `api_token_invalid` (unknown, expired or revoked access credential, or an inactive client). Carries `WWW-Authenticate`. Request a new token.","headers":{"WWW-Authenticate":{"schema":{"type":"string"}},"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseWithCode"}}}},"402":{"description":"`auth_invalid_or_expired_license`: the account's license is not valid.","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseWithCode"}}}},"403":{"description":"`api_origin_forbidden` (the request did not come through the public API endpoint), `api_insufficient_scope` (the client lacks `accounting:read`; carries `WWW-Authenticate`), `api_company_not_allowed` (the client cannot access the company, whether it exists or not) or `api_company_param_conflict` (a `companyId` query parameter that does not match the path).","headers":{"WWW-Authenticate":{"description":"Only with `api_insufficient_scope`.","schema":{"type":"string"}},"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseWithCode"}}}},"429":{"description":"`api_rate_limited`: too many requests for the account or the client. Retry after `Retry-After` seconds.","headers":{"Retry-After":{"$ref":"#/components/headers/RetryAfter"},"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseWithCode"},"examples":{"api_rate_limited":{"$ref":"#/components/examples/PublicApiRateLimited"}}}}},"500":{"description":"`internal_server_error`: unexpected failure. Safe to retry with backoff.","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseWithCode"}}}},"503":{"description":"`public_api_disabled` (the public API is not enabled for the client's account, during the invitation-only preview, or has been switched off) or `api_temporarily_unavailable` (a dependency failed). Retry `api_temporarily_unavailable` after `Retry-After` seconds. Do not retry `public_api_disabled` at that pace: back off exponentially, with at most 5 minutes between attempts, or stop and contact Fibady quoting `X-Request-Id`.","headers":{"Retry-After":{"$ref":"#/components/headers/RetryAfter"},"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseWithCode"}}}}},"security":[{"oauth2":["accounting:read"]}],"parameters":[{"name":"companyId","in":"path","required":true,"description":"Company whose chart of accounts is listed. It must be one the client can access.","schema":{"type":"string"}},{"name":"limit","in":"query","required":false,"description":"Page size, 1 to 500. Defaults to 500.","schema":{"type":"integer","minimum":1,"maximum":500,"default":500}},{"name":"cursor","in":"query","required":false,"description":"Opaque `nextCursor` from the previous page of the same company.","schema":{"type":"string","maxLength":2100}}],"x-codeSamples":[{"lang":"Shell","label":"curl","source":": \"${FIBADY_ACCESS_TOKEN:?set it to the access_token of POST /v1/oauth/token, or to the output of fibady_token after the Quickstart}\" &&\nprintf 'header = \"Authorization: Bearer %s\"\\n' \"$FIBADY_ACCESS_TOKEN\" \\\n  | curl -sS -K - 'https://public-api.fibady.com/v1/companies/01K8EXAMP0C0MPANY000000001/accounts'"}]}},"/v1/companies/{companyId}/accounts/batch":{"post":{"operationId":"upsertAccounts","summary":"Upsert accounts","description":"Upserts up to 500 accounts of the company's chart of accounts. An account is identified by its `code`. A code that already exists in the company, whatever loaded it (a CSV import, manual entry), is ADOPTED: the account keeps its identifier, its classification and its journal entries, takes the `name` sent, and from then on belongs to the API. A code that does not exist creates the account. Accounts are never deleted or deactivated through this operation: sending a new code for an existing account creates another one and leaves the previous one in place. Accounts of the chart that the batch does not name are not touched.\n\nA classification hint that is not sent, or is `null`, keeps the stored one; a hint cannot be cleared. Accounts of the batch that end up unclassified get the company's code-prefix rules applied.\n\nAllowed only when the company's journal entries or invoices are fed by the API and neither comes from an ERP connector; otherwise 409.\n\nValidation is atomic: if any record is invalid, the response is 422 and nothing is written. The load itself is not atomic; after a 5xx or a timeout, send the same batch again: records already loaded come back as `unchanged`. In `results`, `externalId` is the account `code`. A record is `unprojectable` with code `other` when its code matches more than one stored account that differ only in leading or trailing spaces.\n\nSend an `Idempotency-Key` to make a retry return the stored response instead of loading again. With `dryRun=true` the batch is only validated.\n\nEvery response carries `X-Request-Id`, `Cache-Control: no-store` and `Pragma: no-cache`. Error messages follow `Accept-Language` (English by default).","tags":["Accounts"],"responses":{"200":{"description":"The batch was loaded or, with `dryRun=true`, validated.","headers":{"Idempotent-Replayed":{"description":"Present, with `true`, only when the response is the replay of an earlier write with the same `Idempotency-Key` and the same body. The body of a replay is the stored one, except `reportsVisibility` and `postProcessing`, which are computed again from the run.","schema":{"type":"string"}},"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicApiBatchResult"}}}},"400":{"description":"`api_invalid_query` (a query parameter other than `dryRun`, a repeated one, or a `dryRun` that is not `true` or `false`) or `api_invalid_json` (the body is not valid JSON).","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseWithCode"}}}},"401":{"description":"`api_token_missing` (no `Authorization: Bearer` header) or `api_token_invalid` (unknown, expired or revoked access credential, or an inactive client). Carries `WWW-Authenticate`. Request a new token.","headers":{"WWW-Authenticate":{"schema":{"type":"string"}},"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseWithCode"}}}},"402":{"description":"`auth_invalid_or_expired_license`: the account's license is not valid.","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseWithCode"}}}},"403":{"description":"`api_origin_forbidden` (the request did not come through the public API endpoint), `api_insufficient_scope` (the client lacks `accounting:write`; carries `WWW-Authenticate`), `api_company_not_allowed` (the client cannot access the company, whether it exists or not) or `api_company_param_conflict` (a `companyId` in the query or in the body that does not match the path: the company is always the one of the path).","headers":{"WWW-Authenticate":{"description":"Only with `api_insufficient_scope`.","schema":{"type":"string"}},"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseWithCode"}}}},"409":{"description":"The batch cannot be loaded now, and nothing was written. `source_not_owned`: the company's accounting is not fed by the API; `details.reason` is `source_not_assigned` (neither journal entries nor invoices have a source) or `source_not_api` (they have one, and none is the API). `erp_api_mix_not_supported`: one of the two is fed by the API and the other by an ERP connector; `details` is empty. `batch_in_progress`: another batch of the company is loading; no `details`. `idempotency_key_in_progress`: a write with the same `Idempotency-Key` is still running; no `details`. The last two carry `Retry-After`.","headers":{"Retry-After":{"description":"Only with `batch_in_progress` and `idempotency_key_in_progress`.","schema":{"type":"integer","minimum":1}},"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicApiErrorWithDetails"},"examples":{"batch_in_progress":{"$ref":"#/components/examples/PublicApiBatchInProgress"}}}}},"413":{"description":"`api_payload_too_large` (the body is larger than 1 MiB) or `api_batch_too_large` (more than 500 records).","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseWithCode"}}}},"422":{"description":"`validation_failed`: some record is invalid, `details` says where, and nothing was written. `idempotency_key_reused`: the `Idempotency-Key` was already used with a different body; no `details`.","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicApiValidationError"},"examples":{"validation_failed":{"$ref":"#/components/examples/PublicApiValidationFailed"}}}}},"429":{"description":"`api_rate_limited` (too many requests for the account or the client), `api_quota_exceeded` (the account's daily write quota is used up) or `api_concurrent_requests_exceeded` (too many writes of the account in flight). Retry after `Retry-After` seconds.","headers":{"Retry-After":{"$ref":"#/components/headers/RetryAfter"},"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseWithCode"},"examples":{"api_rate_limited":{"$ref":"#/components/examples/PublicApiRateLimited"}}}}},"500":{"description":"`internal_server_error`: unexpected failure. The batch may be partially loaded; send it again.","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseWithCode"}}}},"503":{"description":"`public_api_disabled` (the public API is not enabled for the client's account, during the invitation-only preview, or has been switched off; nothing was written) or `api_temporarily_unavailable` (a dependency failed, or there was not enough time left to start the load). After `api_temporarily_unavailable` the batch may be partially loaded: send it again after `Retry-After` seconds. Do not retry `public_api_disabled` at that pace: back off exponentially, with at most 5 minutes between attempts, or stop and contact Fibady quoting `X-Request-Id`.","headers":{"Retry-After":{"$ref":"#/components/headers/RetryAfter"},"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseWithCode"}}}}},"security":[{"oauth2":["accounting:write"]}],"parameters":[{"name":"companyId","in":"path","required":true,"description":"Company whose chart of accounts is written. It must be one the client can access.","schema":{"type":"string"}},{"name":"dryRun","in":"query","required":false,"description":"`true` validates the batch and writes nothing: no run, no quota. Only `true` or `false`; any other value, or any other query parameter, is a 400.","schema":{"type":"boolean"}},{"name":"Idempotency-Key","in":"header","required":false,"description":"Makes the write idempotent: the same key with the same body returns the stored response, with `Idempotent-Replayed: true`. 1 to 255 printable ASCII characters, no spaces. Ignored with `dryRun=true`.","schema":{"type":"string","pattern":"^[\\x21-\\x7E]{1,255}$"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"properties":{"items":{"type":"array","minItems":1,"maxItems":500,"description":"The accounts. A `code` may appear only once in the batch.","items":{"type":"object","additionalProperties":false,"properties":{"code":{"type":"string","minLength":1,"maxLength":255,"description":"Account code: the identity of the account in the company. No control characters and no leading or trailing whitespace."},"name":{"type":"string","minLength":1,"pattern":"\\S","description":"Account name. Stored as sent; it cannot be empty or blank."},"classificationHint":{"type":["string","null"],"minLength":1,"pattern":"\\S","description":"Free text that helps classify the account. Omitted or `null` keeps the stored hint. It cannot be empty or blank."},"flowTypeHint":{"type":["string","null"],"minLength":1,"pattern":"\\S","description":"Free text that helps classify the account's cash flow. Omitted or `null` keeps the stored hint. It cannot be empty or blank."}},"required":["code","name"]}}},"required":["items"]},"examples":{"chart_of_accounts":{"summary":"Four accounts of the chart of accounts","value":{"items":[{"code":"7000001","name":"Sales of goods","classificationHint":"Revenue from sales of goods"},{"code":"6210001","name":"Office rent"},{"code":"4000001","name":"Suppliers"},{"code":"5720001","name":"Main bank account"}]}}}}}},"x-codeSamples":[{"lang":"Shell","label":"curl","source":": \"${FIBADY_ACCESS_TOKEN:?set it to the access_token of POST /v1/oauth/token, or to the output of fibady_token after the Quickstart}\" &&\nprintf 'header = \"Authorization: Bearer %s\"\\n' \"$FIBADY_ACCESS_TOKEN\" \\\n  | curl -sS -K - 'https://public-api.fibady.com/v1/companies/01K8EXAMP0C0MPANY000000001/accounts/batch' \\\n    -H 'Content-Type: application/json' \\\n    -H 'Idempotency-Key: example-accounts-batch-0001' \\\n    --data-binary '{\n      \"items\": [\n        {\n          \"code\": \"7000001\",\n          \"name\": \"Sales of goods\",\n          \"classificationHint\": \"Revenue from sales of goods\"\n        },\n        {\n          \"code\": \"6210001\",\n          \"name\": \"Office rent\"\n        },\n        {\n          \"code\": \"4000001\",\n          \"name\": \"Suppliers\"\n        },\n        {\n          \"code\": \"5720001\",\n          \"name\": \"Main bank account\"\n        }\n      ]\n    }'"}]}},"/v1/companies/{companyId}/analytic-values":{"get":{"operationId":"listAnalyticValues","summary":"List analytic values","description":"The analytic values written to this company with `POST /v1/companies/{companyId}/analytic-values/batch`, one summary per record: its `externalId`, `projected` (with its `internalId`) or `pending`, and `updatedAt`. It returns nothing else of the record. Use it to reconcile what Fibady holds with your own system. Any client of the account with access to the company can list them, not only the one that wrote them.\n\nOnly records written through the API are listed: a analytic value that another source loaded (manual entry, a CSV import, an ERP connector) is not. Deleted records are not listed either.\n\nRecords come in an internal order, not by `externalId`. Paginate with `cursor` until `nextCursor` is `null`; a page may be shorter than `limit`, even empty, with a non-null `nextCursor`.\n\nFor an incremental sync, pass `updatedSince` to get only the records changed since then. Every page of a pass returns the same `asOf`: start the next pass with `updatedSince` set to it, and no change in between is missed (a record may appear in two passes). `updatedSince` needs a time zone offset: send `Z`, or percent-encode the `+` of a positive offset as `%2B` (an unencoded `+` reads as a space and gives 400).\n\nEvery response carries `X-Request-Id`, `Cache-Control: no-store` and `Pragma: no-cache`. Only the 429 and the 503 carry `Retry-After`. Error messages follow `Accept-Language` (English by default).","tags":["Analytic values"],"responses":{"200":{"description":"One page of analytic values.","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicApiRecordPage"}}}},"400":{"description":"`api_invalid_query`: an unknown or repeated query parameter, a `limit` that is not an integer from 1 to 500, an `updatedSince` that is not an RFC 3339 date-time with a time zone offset, or a `cursor` that is malformed, comes from another list or company, or is sent with another `updatedSince`.","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseWithCode"}}}},"401":{"description":"`api_token_missing` (no `Authorization: Bearer` header) or `api_token_invalid` (unknown, expired or revoked access credential, or an inactive client). Carries `WWW-Authenticate`. Request a new token.","headers":{"WWW-Authenticate":{"schema":{"type":"string"}},"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseWithCode"}}}},"402":{"description":"`auth_invalid_or_expired_license`: the account's license is not valid.","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseWithCode"}}}},"403":{"description":"`api_origin_forbidden` (the request did not come through the public API endpoint), `api_insufficient_scope` (the client lacks `accounting:read`; carries `WWW-Authenticate`), `api_company_not_allowed` (the client cannot access the company, whether it exists or not) or `api_company_param_conflict` (a `companyId` query parameter that does not match the path).","headers":{"WWW-Authenticate":{"description":"Only with `api_insufficient_scope`.","schema":{"type":"string"}},"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseWithCode"}}}},"429":{"description":"`api_rate_limited`: too many requests for the account or the client. Retry after `Retry-After` seconds.","headers":{"Retry-After":{"$ref":"#/components/headers/RetryAfter"},"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseWithCode"},"examples":{"api_rate_limited":{"$ref":"#/components/examples/PublicApiRateLimited"}}}}},"500":{"description":"`internal_server_error`: unexpected failure. Safe to retry with backoff.","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseWithCode"}}}},"503":{"description":"`public_api_disabled` (the public API is not enabled for the client's account, during the invitation-only preview, or has been switched off) or `api_temporarily_unavailable` (a dependency failed). Retry `api_temporarily_unavailable` after `Retry-After` seconds. Do not retry `public_api_disabled` at that pace: back off exponentially, with at most 5 minutes between attempts, or stop and contact Fibady quoting `X-Request-Id`.","headers":{"Retry-After":{"$ref":"#/components/headers/RetryAfter"},"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseWithCode"}}}}},"security":[{"oauth2":["accounting:read"]}],"parameters":[{"name":"companyId","in":"path","required":true,"description":"Company whose analytic values are listed. It must be one the client can access.","schema":{"type":"string"}},{"name":"limit","in":"query","required":false,"description":"Page size, 1 to 500. Defaults to 500.","schema":{"type":"integer","minimum":1,"maximum":500,"default":500}},{"name":"cursor","in":"query","required":false,"description":"Opaque `nextCursor` from the previous page of the same list and company. With it, `updatedSince` may be omitted; if sent, it must be the value the pass started with.","schema":{"type":"string","maxLength":2048}},{"name":"updatedSince","in":"query","required":false,"description":"Only records changed at or after this instant: an RFC 3339 date-time with a time zone offset (`Z` or `±HH:MM`) and up to 6 fractional digits, such as `2026-10-10T08:15:30.123456Z`. Percent-encode `+` as `%2B`. A future instant gives an empty last page.","schema":{"type":"string","format":"date-time"}}],"x-codeSamples":[{"lang":"Shell","label":"curl","source":": \"${FIBADY_ACCESS_TOKEN:?set it to the access_token of POST /v1/oauth/token, or to the output of fibady_token after the Quickstart}\" &&\nprintf 'header = \"Authorization: Bearer %s\"\\n' \"$FIBADY_ACCESS_TOKEN\" \\\n  | curl -sS -K - 'https://public-api.fibady.com/v1/companies/01K8EXAMP0C0MPANY000000001/analytic-values'"}]}},"/v1/companies/{companyId}/analytic-values/{externalId}":{"get":{"operationId":"getAnalyticValue","summary":"Get analytic value state","description":"Whether an analytic value written to this company with `POST /v1/companies/{companyId}/analytic-values/batch` is in Fibady, looked up by the `externalId` it was written with: `projected`, with its `internalId`, or `pending`. It returns nothing else of the record. Use it to confirm a record after a write whose result you could not read, before sending it again. Any client of the account with access to the company can read it, not only the one that wrote it.\n\nOnly records written through the API are found: an analytic value that another source loaded (manual entry, a CSV import, an ERP connector) gives 404 even if it carries the same identifier. An `externalId` that does not exist, belongs to another company or is malformed gives the same 404.\n\nPercent-encode the `externalId` in the path.\n\nEvery response carries `X-Request-Id`, `Cache-Control: no-store` and `Pragma: no-cache`. Only the 429 and the 503 carry `Retry-After`. Error messages follow `Accept-Language` (English by default).","tags":["Analytic values"],"responses":{"200":{"description":"The state of the record.","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicApiRecordStatus"}}}},"400":{"description":"`api_invalid_query`: the operation takes no query parameters, and one was sent.","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseWithCode"}}}},"401":{"description":"`api_token_missing` (no `Authorization: Bearer` header) or `api_token_invalid` (unknown, expired or revoked access credential, or an inactive client). Carries `WWW-Authenticate`. Request a new token.","headers":{"WWW-Authenticate":{"schema":{"type":"string"}},"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseWithCode"}}}},"402":{"description":"`auth_invalid_or_expired_license`: the account's license is not valid.","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseWithCode"}}}},"403":{"description":"`api_origin_forbidden` (the request did not come through the public API endpoint), `api_insufficient_scope` (the client lacks `accounting:read`; carries `WWW-Authenticate`), `api_company_not_allowed` (the client cannot access the company, whether it exists or not) or `api_company_param_conflict` (a `companyId` query parameter that does not match the path).","headers":{"WWW-Authenticate":{"description":"Only with `api_insufficient_scope`.","schema":{"type":"string"}},"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseWithCode"}}}},"404":{"description":"`api_resource_not_found`: there is no such analytic value written through the API for this company. The same response for an `externalId` that does not exist, belongs to another company, belongs to a record from another source or is malformed.","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseWithCode"}}}},"429":{"description":"`api_rate_limited`: too many requests for the account or the client. Retry after `Retry-After` seconds.","headers":{"Retry-After":{"$ref":"#/components/headers/RetryAfter"},"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseWithCode"},"examples":{"api_rate_limited":{"$ref":"#/components/examples/PublicApiRateLimited"}}}}},"500":{"description":"`internal_server_error`: unexpected failure. Safe to retry with backoff.","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseWithCode"}}}},"503":{"description":"`public_api_disabled` (the public API is not enabled for the client's account, during the invitation-only preview, or has been switched off) or `api_temporarily_unavailable` (a dependency failed). Retry `api_temporarily_unavailable` after `Retry-After` seconds. Do not retry `public_api_disabled` at that pace: back off exponentially, with at most 5 minutes between attempts, or stop and contact Fibady quoting `X-Request-Id`.","headers":{"Retry-After":{"$ref":"#/components/headers/RetryAfter"},"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseWithCode"}}}}},"security":[{"oauth2":["accounting:read"]}],"parameters":[{"name":"companyId","in":"path","required":true,"description":"Company the record was written to. It must be one the client can access.","schema":{"type":"string"}},{"name":"externalId","in":"path","required":true,"description":"The `externalId` the analytic value was written with, percent-encoded: 1 to 255 characters, without control characters and without leading or trailing whitespace.","schema":{"type":"string","minLength":1,"maxLength":255}}],"x-codeSamples":[{"lang":"Shell","label":"curl","source":": \"${FIBADY_ACCESS_TOKEN:?set it to the access_token of POST /v1/oauth/token, or to the output of fibady_token after the Quickstart}\" &&\nprintf 'header = \"Authorization: Bearer %s\"\\n' \"$FIBADY_ACCESS_TOKEN\" \\\n  | curl -sS -K - 'https://public-api.fibady.com/v1/companies/01K8EXAMP0C0MPANY000000001/analytic-values/example-region-north'"}]}},"/v1/companies/{companyId}/analytic-values/batch":{"post":{"operationId":"upsertAnalyticValues","summary":"Upsert analytic values","description":"Upserts up to 500 analytic values of the company. A value is identified by its `externalId`, which is unique within the company ACROSS all dimensions and is scoped to the company of the path: the same `externalId` sent to two companies creates two different values, and a batch never changes the values of another company. A value never moves to another dimension: sending an `externalId` that already is a value of a different dimension is a 422 (`duplicate_external_id`); to move a value, send it with a new `externalId`.\n\n`dimension` is the name of the dimension the value belongs to, chosen by the client (for example `region` or `project`). It is case-sensitive. Dimensions are shared by all the companies of the account and are created by the first batch that names them, up to 5 per account; a batch that would need a sixth one is a 422 (`analytic_dimension_limit_reached`) on every record that uses it. Dimensions cannot be renamed or deleted through the API, and a batch never changes a dimension that already exists. A dimension name whose code is already taken in the account by a dimension that did not come from the API cannot be used: 409 `analytic_dimension_code_reserved`.\n\nEvery upsert writes the whole value: `name` not sent, or `null`, is the `externalId`, and `active` not sent, or `null`, is `true`. Values are never deleted through this operation; send `active: false` to deactivate one.\n\nAllowed only when the company's journal entries or invoices are fed by the API and neither comes from an ERP connector; otherwise 409.\n\nValidation is atomic: if any record is invalid, the response is 422 and nothing is written. The load itself is not atomic; after a 5xx or a timeout, send the same batch again: records already loaded come back as `unchanged`. A record is `unprojectable` with code `other` when its dimension could not be created at load time, because another company of the account filled the limit or took its code in the meantime; the rest of the batch is loaded. A batch of analytic values changes nothing in the reports, so `reportsVisibility` is `visible`.\n\nSend an `Idempotency-Key` to make a retry return the stored response instead of loading again. With `dryRun=true` the batch is only validated, and no dimension is created.\n\nEvery response carries `X-Request-Id`, `Cache-Control: no-store` and `Pragma: no-cache`. Error messages follow `Accept-Language` (English by default).","tags":["Analytic values"],"responses":{"200":{"description":"The batch was loaded or, with `dryRun=true`, validated.","headers":{"Idempotent-Replayed":{"description":"Present, with `true`, only when the response is the replay of an earlier write with the same `Idempotency-Key` and the same body. The body of a replay is the stored one, except `reportsVisibility` and `postProcessing`, which are computed again from the run.","schema":{"type":"string"}},"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicApiBatchResult"}}}},"400":{"description":"`api_invalid_query` (a query parameter other than `dryRun`, a repeated one, or a `dryRun` that is not `true` or `false`) or `api_invalid_json` (the body is not valid JSON).","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseWithCode"}}}},"401":{"description":"`api_token_missing` (no `Authorization: Bearer` header) or `api_token_invalid` (unknown, expired or revoked access credential, or an inactive client). Carries `WWW-Authenticate`. Request a new token.","headers":{"WWW-Authenticate":{"schema":{"type":"string"}},"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseWithCode"}}}},"402":{"description":"`auth_invalid_or_expired_license`: the account's license is not valid.","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseWithCode"}}}},"403":{"description":"`api_origin_forbidden` (the request did not come through the public API endpoint), `api_insufficient_scope` (the client lacks `accounting:write`; carries `WWW-Authenticate`), `api_company_not_allowed` (the client cannot access the company, whether it exists or not) or `api_company_param_conflict` (a `companyId` in the query or in the body that does not match the path: the company is always the one of the path).","headers":{"WWW-Authenticate":{"description":"Only with `api_insufficient_scope`.","schema":{"type":"string"}},"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseWithCode"}}}},"409":{"description":"The batch cannot be loaded now, and nothing was written. `source_not_owned`: the company's accounting is not fed by the API; `details.reason` is `source_not_assigned` (neither journal entries nor invoices have a source) or `source_not_api` (they have one, and none is the API). `erp_api_mix_not_supported`: one of the two is fed by the API and the other by an ERP connector; `details` is empty. `analytic_dimension_code_reserved`: the code of some dimension of the batch is already taken in the account by a dimension that did not come from the API; `details.dimensions` lists those dimension names, as sent. `batch_in_progress`: another batch of the company is loading; no `details`. `idempotency_key_in_progress`: a write with the same `Idempotency-Key` is still running; no `details`. The last two carry `Retry-After`.","headers":{"Retry-After":{"description":"Only with `batch_in_progress` and `idempotency_key_in_progress`.","schema":{"type":"integer","minimum":1}},"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicApiErrorWithDetails"},"examples":{"batch_in_progress":{"$ref":"#/components/examples/PublicApiBatchInProgress"}}}}},"413":{"description":"`api_payload_too_large` (the body is larger than 1 MiB) or `api_batch_too_large` (more than 500 records).","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseWithCode"}}}},"422":{"description":"`validation_failed`: some record is invalid, `details` says where, and nothing was written. `idempotency_key_reused`: the `Idempotency-Key` was already used with a different body; no `details`.","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicApiValidationError"},"examples":{"validation_failed":{"$ref":"#/components/examples/PublicApiValidationFailed"}}}}},"429":{"description":"`api_rate_limited` (too many requests for the account or the client), `api_quota_exceeded` (the account's daily write quota is used up) or `api_concurrent_requests_exceeded` (too many writes of the account in flight). Retry after `Retry-After` seconds.","headers":{"Retry-After":{"$ref":"#/components/headers/RetryAfter"},"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseWithCode"},"examples":{"api_rate_limited":{"$ref":"#/components/examples/PublicApiRateLimited"}}}}},"500":{"description":"`internal_server_error`: unexpected failure. The batch may be partially loaded; send it again.","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseWithCode"}}}},"503":{"description":"`public_api_disabled` (the public API is not enabled for the client's account, during the invitation-only preview, or has been switched off; nothing was written) or `api_temporarily_unavailable` (a dependency failed, or there was not enough time left to start the load). After `api_temporarily_unavailable` the batch may be partially loaded: send it again after `Retry-After` seconds. Do not retry `public_api_disabled` at that pace: back off exponentially, with at most 5 minutes between attempts, or stop and contact Fibady quoting `X-Request-Id`.","headers":{"Retry-After":{"$ref":"#/components/headers/RetryAfter"},"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseWithCode"}}}}},"security":[{"oauth2":["accounting:write"]}],"parameters":[{"name":"companyId","in":"path","required":true,"description":"Company whose analytic values are written. It must be one the client can access.","schema":{"type":"string"}},{"name":"dryRun","in":"query","required":false,"description":"`true` validates the batch and writes nothing: no run, no quota. Only `true` or `false`; any other value, or any other query parameter, is a 400.","schema":{"type":"boolean"}},{"name":"Idempotency-Key","in":"header","required":false,"description":"Makes the write idempotent: the same key with the same body returns the stored response, with `Idempotent-Replayed: true`. 1 to 255 printable ASCII characters, no spaces. Ignored with `dryRun=true`.","schema":{"type":"string","pattern":"^[\\x21-\\x7E]{1,255}$"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"properties":{"items":{"type":"array","minItems":1,"maxItems":500,"description":"The analytic values. An `externalId` may appear only once in the batch.","items":{"type":"object","additionalProperties":false,"properties":{"externalId":{"type":"string","minLength":1,"maxLength":255,"description":"Your identifier of the value: its identity within the company, in every dimension. No control characters and no leading or trailing whitespace."},"dimension":{"type":"string","minLength":1,"maxLength":64,"description":"Name of the dimension the value belongs to. Case-sensitive. No control characters and no leading or trailing whitespace."},"name":{"type":["string","null"],"minLength":1,"pattern":"\\S","description":"Name of the value. Stored as sent; it cannot be empty or blank. Omitted or `null` is the `externalId`."},"active":{"type":["boolean","null"],"default":true,"description":"`false` deactivates the value. Omitted or `null` is `true`."}},"required":["externalId","dimension"]}}},"required":["items"]},"examples":{"regions":{"summary":"Two values of the dimension Region","value":{"items":[{"externalId":"example-region-north","dimension":"Region","name":"North"},{"externalId":"example-region-south","dimension":"Region","name":"South"}]}}}}}},"x-codeSamples":[{"lang":"Shell","label":"curl","source":": \"${FIBADY_ACCESS_TOKEN:?set it to the access_token of POST /v1/oauth/token, or to the output of fibady_token after the Quickstart}\" &&\nprintf 'header = \"Authorization: Bearer %s\"\\n' \"$FIBADY_ACCESS_TOKEN\" \\\n  | curl -sS -K - 'https://public-api.fibady.com/v1/companies/01K8EXAMP0C0MPANY000000001/analytic-values/batch' \\\n    -H 'Content-Type: application/json' \\\n    -H 'Idempotency-Key: example-analytic-values-batch-0001' \\\n    --data-binary '{\n      \"items\": [\n        {\n          \"externalId\": \"example-region-north\",\n          \"dimension\": \"Region\",\n          \"name\": \"North\"\n        },\n        {\n          \"externalId\": \"example-region-south\",\n          \"dimension\": \"Region\",\n          \"name\": \"South\"\n        }\n      ]\n    }'"}]}},"/v1/companies/{companyId}/bank-accounts":{"get":{"operationId":"listBankAccounts","summary":"List bank accounts","description":"The bank accounts written to this company with `POST /v1/companies/{companyId}/bank-accounts/batch`, one summary per record: its `externalId`, `projected` (with its `internalId`) or `pending`, and `updatedAt`. It returns nothing else of the record. Use it to reconcile what Fibady holds with your own system. Any client of the account with access to the company can list them, not only the one that wrote them.\n\nOnly records written through the API are listed: a bank account that another source loaded (manual entry, a CSV import, an ERP connector) is not. Deleted records are not listed either.\n\nRecords come in an internal order, not by `externalId`. Paginate with `cursor` until `nextCursor` is `null`; a page may be shorter than `limit`, even empty, with a non-null `nextCursor`.\n\nFor an incremental sync, pass `updatedSince` to get only the records changed since then. Every page of a pass returns the same `asOf`: start the next pass with `updatedSince` set to it, and no change in between is missed (a record may appear in two passes). `updatedSince` needs a time zone offset: send `Z`, or percent-encode the `+` of a positive offset as `%2B` (an unencoded `+` reads as a space and gives 400).\n\nEvery response carries `X-Request-Id`, `Cache-Control: no-store` and `Pragma: no-cache`. Only the 429 and the 503 carry `Retry-After`. Error messages follow `Accept-Language` (English by default).","tags":["Bank accounts"],"responses":{"200":{"description":"One page of bank accounts.","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicApiRecordPage"}}}},"400":{"description":"`api_invalid_query`: an unknown or repeated query parameter, a `limit` that is not an integer from 1 to 500, an `updatedSince` that is not an RFC 3339 date-time with a time zone offset, or a `cursor` that is malformed, comes from another list or company, or is sent with another `updatedSince`.","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseWithCode"}}}},"401":{"description":"`api_token_missing` (no `Authorization: Bearer` header) or `api_token_invalid` (unknown, expired or revoked access credential, or an inactive client). Carries `WWW-Authenticate`. Request a new token.","headers":{"WWW-Authenticate":{"schema":{"type":"string"}},"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseWithCode"}}}},"402":{"description":"`auth_invalid_or_expired_license`: the account's license is not valid.","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseWithCode"}}}},"403":{"description":"`api_origin_forbidden` (the request did not come through the public API endpoint), `api_insufficient_scope` (the client lacks `banking:read`; carries `WWW-Authenticate`), `api_company_not_allowed` (the client cannot access the company, whether it exists or not) or `api_company_param_conflict` (a `companyId` query parameter that does not match the path).","headers":{"WWW-Authenticate":{"description":"Only with `api_insufficient_scope`.","schema":{"type":"string"}},"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseWithCode"}}}},"429":{"description":"`api_rate_limited`: too many requests for the account or the client. Retry after `Retry-After` seconds.","headers":{"Retry-After":{"$ref":"#/components/headers/RetryAfter"},"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseWithCode"},"examples":{"api_rate_limited":{"$ref":"#/components/examples/PublicApiRateLimited"}}}}},"500":{"description":"`internal_server_error`: unexpected failure. Safe to retry with backoff.","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseWithCode"}}}},"503":{"description":"`public_api_disabled` (the public API is not enabled for the client's account, during the invitation-only preview, or has been switched off) or `api_temporarily_unavailable` (a dependency failed). Retry `api_temporarily_unavailable` after `Retry-After` seconds. Do not retry `public_api_disabled` at that pace: back off exponentially, with at most 5 minutes between attempts, or stop and contact Fibady quoting `X-Request-Id`.","headers":{"Retry-After":{"$ref":"#/components/headers/RetryAfter"},"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseWithCode"}}}}},"security":[{"oauth2":["banking:read"]}],"parameters":[{"name":"companyId","in":"path","required":true,"description":"Company whose bank accounts are listed. It must be one the client can access.","schema":{"type":"string"}},{"name":"limit","in":"query","required":false,"description":"Page size, 1 to 500. Defaults to 500.","schema":{"type":"integer","minimum":1,"maximum":500,"default":500}},{"name":"cursor","in":"query","required":false,"description":"Opaque `nextCursor` from the previous page of the same list and company. With it, `updatedSince` may be omitted; if sent, it must be the value the pass started with.","schema":{"type":"string","maxLength":2048}},{"name":"updatedSince","in":"query","required":false,"description":"Only records changed at or after this instant: an RFC 3339 date-time with a time zone offset (`Z` or `±HH:MM`) and up to 6 fractional digits, such as `2026-10-10T08:15:30.123456Z`. Percent-encode `+` as `%2B`. A future instant gives an empty last page.","schema":{"type":"string","format":"date-time"}}],"x-codeSamples":[{"lang":"Shell","label":"curl","source":": \"${FIBADY_ACCESS_TOKEN:?set it to the access_token of POST /v1/oauth/token, or to the output of fibady_token after the Quickstart}\" &&\nprintf 'header = \"Authorization: Bearer %s\"\\n' \"$FIBADY_ACCESS_TOKEN\" \\\n  | curl -sS -K - 'https://public-api.fibady.com/v1/companies/01K8EXAMP0C0MPANY000000001/bank-accounts'"}]}},"/v1/companies/{companyId}/bank-accounts/{externalId}":{"get":{"operationId":"getBankAccount","summary":"Get bank account state","description":"Whether a bank account written to this company with `POST /v1/companies/{companyId}/bank-accounts/batch` is in Fibady, looked up by the `externalId` it was written with: `projected`, with its `internalId`, or `pending`. It returns nothing else of the record. Use it to confirm a record after a write whose result you could not read, before sending it again. Any client of the account with access to the company can read it, not only the one that wrote it.\n\nOnly records written through the API are found: a bank account that another source loaded (manual entry, a CSV import, an ERP connector) gives 404 even if it carries the same identifier. An `externalId` that does not exist, belongs to another company or is malformed gives the same 404.\n\nPercent-encode the `externalId` in the path.\n\nEvery response carries `X-Request-Id`, `Cache-Control: no-store` and `Pragma: no-cache`. Only the 429 and the 503 carry `Retry-After`. Error messages follow `Accept-Language` (English by default).","tags":["Bank accounts"],"responses":{"200":{"description":"The state of the record.","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicApiRecordStatus"}}}},"400":{"description":"`api_invalid_query`: the operation takes no query parameters, and one was sent.","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseWithCode"}}}},"401":{"description":"`api_token_missing` (no `Authorization: Bearer` header) or `api_token_invalid` (unknown, expired or revoked access credential, or an inactive client). Carries `WWW-Authenticate`. Request a new token.","headers":{"WWW-Authenticate":{"schema":{"type":"string"}},"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseWithCode"}}}},"402":{"description":"`auth_invalid_or_expired_license`: the account's license is not valid.","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseWithCode"}}}},"403":{"description":"`api_origin_forbidden` (the request did not come through the public API endpoint), `api_insufficient_scope` (the client lacks `banking:read`; carries `WWW-Authenticate`), `api_company_not_allowed` (the client cannot access the company, whether it exists or not) or `api_company_param_conflict` (a `companyId` query parameter that does not match the path).","headers":{"WWW-Authenticate":{"description":"Only with `api_insufficient_scope`.","schema":{"type":"string"}},"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseWithCode"}}}},"404":{"description":"`api_resource_not_found`: there is no such bank account written through the API for this company. The same response for an `externalId` that does not exist, belongs to another company, belongs to a record from another source or is malformed.","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseWithCode"}}}},"429":{"description":"`api_rate_limited`: too many requests for the account or the client. Retry after `Retry-After` seconds.","headers":{"Retry-After":{"$ref":"#/components/headers/RetryAfter"},"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseWithCode"},"examples":{"api_rate_limited":{"$ref":"#/components/examples/PublicApiRateLimited"}}}}},"500":{"description":"`internal_server_error`: unexpected failure. Safe to retry with backoff.","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseWithCode"}}}},"503":{"description":"`public_api_disabled` (the public API is not enabled for the client's account, during the invitation-only preview, or has been switched off) or `api_temporarily_unavailable` (a dependency failed). Retry `api_temporarily_unavailable` after `Retry-After` seconds. Do not retry `public_api_disabled` at that pace: back off exponentially, with at most 5 minutes between attempts, or stop and contact Fibady quoting `X-Request-Id`.","headers":{"Retry-After":{"$ref":"#/components/headers/RetryAfter"},"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseWithCode"}}}}},"security":[{"oauth2":["banking:read"]}],"parameters":[{"name":"companyId","in":"path","required":true,"description":"Company the record was written to. It must be one the client can access.","schema":{"type":"string"}},{"name":"externalId","in":"path","required":true,"description":"The `externalId` the bank account was written with, percent-encoded: 1 to 255 characters, without control characters and without leading or trailing whitespace.","schema":{"type":"string","minLength":1,"maxLength":255}}],"x-codeSamples":[{"lang":"Shell","label":"curl","source":": \"${FIBADY_ACCESS_TOKEN:?set it to the access_token of POST /v1/oauth/token, or to the output of fibady_token after the Quickstart}\" &&\nprintf 'header = \"Authorization: Bearer %s\"\\n' \"$FIBADY_ACCESS_TOKEN\" \\\n  | curl -sS -K - 'https://public-api.fibady.com/v1/companies/01K8EXAMP0C0MPANY000000001/bank-accounts/example-bank-account-0001'"}]}},"/v1/companies/{companyId}/bank-accounts/batch":{"post":{"operationId":"upsertBankAccounts","summary":"Upsert bank accounts","description":"Upserts up to 500 money accounts of the company: bank accounts, cash accounts and cards. An account is identified by its `externalId`, which is scoped to the company of the path: the same `externalId` sent to two companies creates two different accounts, and a batch never changes the accounts of another company. An account that did not come from the API (created in Fibady, imported from a file, synced from an ERP connector) is never matched or changed, even with the same name: sending it creates another account.\n\nAccount names are unique within a company. When the name is already taken by another account, also by one earlier in the same batch, the new account is created as `<name> (api)`; the record still comes back as `created`. If no free name is found, that record comes back as `unprojectable` with `bank_account_name_exhausted` and the rest of the batch is loaded.\n\nOn an account that already exists: `name` is never changed, the one sent is ignored; `currency` cannot change, a different one is a 422 with `invalid_currency`; `openingBalance` and `openingBalanceDate` replace the stored ones when sent and are kept when omitted; `archived: true` closes the account, `false` reopens it and omitting it leaves it as it is. `type`, `iban`, `accountNumber` and `bic` are set when the account is created and are not changed by a later batch. The record comes back as `updated` whenever its content differs from the one last sent, even if nothing changed in Fibady. Accounts are never deleted through this operation.\n\nAllowed only when the company's bank data is fed by the API; otherwise 409. It does not depend on where journal entries and invoices come from: bank data fed by the API can coexist with an ERP connector.\n\nValidation is atomic: if any record is invalid, the response is 422 and nothing is written. The load itself is not atomic; after a 5xx or a timeout, send the same batch again: records already loaded come back as `unchanged`. A batch of bank accounts changes nothing in the reports, so `reportsVisibility` is `visible`.\n\nSend an `Idempotency-Key` to make a retry return the stored response instead of loading again. With `dryRun=true` the batch is only validated.\n\nEvery response carries `X-Request-Id`, `Cache-Control: no-store` and `Pragma: no-cache`. Error messages follow `Accept-Language` (English by default).","tags":["Bank accounts"],"responses":{"200":{"description":"The batch was loaded or, with `dryRun=true`, validated.","headers":{"Idempotent-Replayed":{"description":"Present, with `true`, only when the response is the replay of an earlier write with the same `Idempotency-Key` and the same body. The body of a replay is the stored one, except `reportsVisibility` and `postProcessing`, which are computed again from the run.","schema":{"type":"string"}},"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicApiBatchResult"}}}},"400":{"description":"`api_invalid_query` (a query parameter other than `dryRun`, a repeated one, or a `dryRun` that is not `true` or `false`) or `api_invalid_json` (the body is not valid JSON).","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseWithCode"}}}},"401":{"description":"`api_token_missing` (no `Authorization: Bearer` header) or `api_token_invalid` (unknown, expired or revoked access credential, or an inactive client). Carries `WWW-Authenticate`. Request a new token.","headers":{"WWW-Authenticate":{"schema":{"type":"string"}},"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseWithCode"}}}},"402":{"description":"`auth_invalid_or_expired_license`: the account's license is not valid.","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseWithCode"}}}},"403":{"description":"`api_origin_forbidden` (the request did not come through the public API endpoint), `api_insufficient_scope` (the client lacks `banking:write`; carries `WWW-Authenticate`), `api_company_not_allowed` (the client cannot access the company, whether it exists or not) or `api_company_param_conflict` (a `companyId` in the query or in the body that does not match the path: the company is always the one of the path).","headers":{"WWW-Authenticate":{"description":"Only with `api_insufficient_scope`.","schema":{"type":"string"}},"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseWithCode"}}}},"409":{"description":"The batch cannot be loaded now, and nothing was written. `source_not_owned`: the company's bank data is not fed by the API; `details.reason` is `source_not_assigned` (the company has no bank source) or `source_not_api` (it has one, and none is the API). `batch_in_progress`: another batch of the company is loading; no `details`. `idempotency_key_in_progress`: a write with the same `Idempotency-Key` is still running; no `details`. The last two carry `Retry-After`.","headers":{"Retry-After":{"description":"Only with `batch_in_progress` and `idempotency_key_in_progress`.","schema":{"type":"integer","minimum":1}},"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicApiErrorWithDetails"},"examples":{"batch_in_progress":{"$ref":"#/components/examples/PublicApiBatchInProgress"}}}}},"413":{"description":"`api_payload_too_large` (the body is larger than 1 MiB) or `api_batch_too_large` (more than 500 records).","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseWithCode"}}}},"422":{"description":"`validation_failed`: some record is invalid, `details` says where, and nothing was written. `idempotency_key_reused`: the `Idempotency-Key` was already used with a different body; no `details`.","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicApiValidationError"},"examples":{"validation_failed":{"$ref":"#/components/examples/PublicApiValidationFailed"}}}}},"429":{"description":"`api_rate_limited` (too many requests for the account or the client), `api_quota_exceeded` (the account's daily write quota is used up) or `api_concurrent_requests_exceeded` (too many writes of the account in flight). Retry after `Retry-After` seconds.","headers":{"Retry-After":{"$ref":"#/components/headers/RetryAfter"},"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseWithCode"},"examples":{"api_rate_limited":{"$ref":"#/components/examples/PublicApiRateLimited"}}}}},"500":{"description":"`internal_server_error`: unexpected failure. The batch may be partially loaded; send it again.","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseWithCode"}}}},"503":{"description":"`public_api_disabled` (the public API is not enabled for the client's account, during the invitation-only preview, or has been switched off; nothing was written) or `api_temporarily_unavailable` (a dependency failed, or there was not enough time left to start the load). After `api_temporarily_unavailable` the batch may be partially loaded: send it again after `Retry-After` seconds. Do not retry `public_api_disabled` at that pace: back off exponentially, with at most 5 minutes between attempts, or stop and contact Fibady quoting `X-Request-Id`.","headers":{"Retry-After":{"$ref":"#/components/headers/RetryAfter"},"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseWithCode"}}}}},"security":[{"oauth2":["banking:write"]}],"parameters":[{"name":"companyId","in":"path","required":true,"description":"Company whose bank accounts are written. It must be one the client can access.","schema":{"type":"string"}},{"name":"dryRun","in":"query","required":false,"description":"`true` validates the batch and writes nothing: no run, no quota. Only `true` or `false`; any other value, or any other query parameter, is a 400.","schema":{"type":"boolean"}},{"name":"Idempotency-Key","in":"header","required":false,"description":"Makes the write idempotent: the same key with the same body returns the stored response, with `Idempotent-Replayed: true`. 1 to 255 printable ASCII characters, no spaces. Ignored with `dryRun=true`.","schema":{"type":"string","pattern":"^[\\x21-\\x7E]{1,255}$"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"properties":{"items":{"type":"array","minItems":1,"maxItems":500,"description":"The accounts. An `externalId` may appear only once in the batch.","items":{"type":"object","additionalProperties":false,"properties":{"externalId":{"type":"string","minLength":1,"maxLength":255,"description":"Your identifier of the account: its identity within the company. No control characters and no leading or trailing whitespace."},"name":{"type":"string","minLength":1,"pattern":"\\S","description":"Account name, as shown in Fibady. Leading and trailing whitespace is removed before storing it; it cannot be empty or blank. Set when the account is created: on an existing account the name sent is ignored."},"currency":{"type":"string","pattern":"^[A-Z]{3}$","description":"Currency of the account: three uppercase letters (ISO 4217, `EUR`). It cannot change once the account exists."},"type":{"type":"string","description":"Kind of account: a bank account, cash or a card. Any other value is a 422. Set when the account is created.","enum":["bank","cash","card"]},"iban":{"type":["string","null"],"minLength":1,"pattern":"\\S","description":"IBAN of the account. Stored as sent, without validating its format. When both `iban` and `accountNumber` are sent, the IBAN is the one stored. Set when the account is created."},"accountNumber":{"type":["string","null"],"minLength":1,"pattern":"\\S","description":"Account number, for accounts without an IBAN. Stored as sent. Set when the account is created."},"bic":{"type":["string","null"],"minLength":1,"pattern":"\\S","description":"BIC or SWIFT code. Stored as sent, without validating its format. Set when the account is created."},"openingBalance":{"type":["string","null"],"pattern":"^-?\\d{1,13}(\\.\\d{1,2})?$","description":"Opening balance, as a decimal string in the currency of the account: up to 13 integer digits and up to 2 decimals, with a dot (`\"1500.25\"`, `\"-320.00\"`). A JSON number is a 422. On a new account, omitted or `null` is `0`; on an existing one, omitted or `null` keeps the stored balance."},"openingBalanceDate":{"type":["string","null"],"format":"date","description":"Date of the opening balance, `YYYY-MM-DD`. Requires `openingBalance`. Omitted or `null` leaves the account without a date when it is created, and keeps the stored one afterwards."},"archived":{"type":["boolean","null"],"description":"`true` closes the account and `false` reopens it. Omitted or `null` changes nothing: a new account is created open, and an existing one keeps its state."}},"required":["externalId","name","currency","type"]}}},"required":["items"]},"examples":{"current_account":{"summary":"A current account with its opening balance","value":{"items":[{"externalId":"example-bank-account-0001","name":"Main account","currency":"EUR","type":"bank","iban":"ES9121000418450200051332","openingBalance":"15000.00","openingBalanceDate":"2026-01-01"}]}}}}}},"x-codeSamples":[{"lang":"Shell","label":"curl","source":": \"${FIBADY_ACCESS_TOKEN:?set it to the access_token of POST /v1/oauth/token, or to the output of fibady_token after the Quickstart}\" &&\nprintf 'header = \"Authorization: Bearer %s\"\\n' \"$FIBADY_ACCESS_TOKEN\" \\\n  | curl -sS -K - 'https://public-api.fibady.com/v1/companies/01K8EXAMP0C0MPANY000000001/bank-accounts/batch' \\\n    -H 'Content-Type: application/json' \\\n    -H 'Idempotency-Key: example-bank-accounts-batch-0001' \\\n    --data-binary '{\n      \"items\": [\n        {\n          \"externalId\": \"example-bank-account-0001\",\n          \"name\": \"Main account\",\n          \"currency\": \"EUR\",\n          \"type\": \"bank\",\n          \"iban\": \"ES9121000418450200051332\",\n          \"openingBalance\": \"15000.00\",\n          \"openingBalanceDate\": \"2026-01-01\"\n        }\n      ]\n    }'"}]}},"/v1/companies/{companyId}/bank-movements":{"get":{"operationId":"listBankMovements","summary":"List bank movements","description":"The bank movements written to this company with `POST /v1/companies/{companyId}/bank-movements/batch`, one summary per record: its `externalId`, `projected` (with its `internalId`) or `pending`, and `updatedAt`. It returns nothing else of the record. Use it to reconcile what Fibady holds with your own system. Any client of the account with access to the company can list them, not only the one that wrote them.\n\nOnly records written through the API are listed: a bank movement that another source loaded (manual entry, a CSV import, an ERP connector) is not. Deleted records are not listed either.\n\nRecords come in an internal order, not by `externalId`. Paginate with `cursor` until `nextCursor` is `null`; a page may be shorter than `limit`, even empty, with a non-null `nextCursor`.\n\nFor an incremental sync, pass `updatedSince` to get only the records changed since then. Every page of a pass returns the same `asOf`: start the next pass with `updatedSince` set to it, and no change in between is missed (a record may appear in two passes). `updatedSince` needs a time zone offset: send `Z`, or percent-encode the `+` of a positive offset as `%2B` (an unencoded `+` reads as a space and gives 400).\n\nEvery response carries `X-Request-Id`, `Cache-Control: no-store` and `Pragma: no-cache`. Only the 429 and the 503 carry `Retry-After`. Error messages follow `Accept-Language` (English by default).","tags":["Bank movements"],"responses":{"200":{"description":"One page of bank movements.","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicApiRecordPage"}}}},"400":{"description":"`api_invalid_query`: an unknown or repeated query parameter, a `limit` that is not an integer from 1 to 500, an `updatedSince` that is not an RFC 3339 date-time with a time zone offset, or a `cursor` that is malformed, comes from another list or company, or is sent with another `updatedSince`.","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseWithCode"}}}},"401":{"description":"`api_token_missing` (no `Authorization: Bearer` header) or `api_token_invalid` (unknown, expired or revoked access credential, or an inactive client). Carries `WWW-Authenticate`. Request a new token.","headers":{"WWW-Authenticate":{"schema":{"type":"string"}},"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseWithCode"}}}},"402":{"description":"`auth_invalid_or_expired_license`: the account's license is not valid.","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseWithCode"}}}},"403":{"description":"`api_origin_forbidden` (the request did not come through the public API endpoint), `api_insufficient_scope` (the client lacks `banking:read`; carries `WWW-Authenticate`), `api_company_not_allowed` (the client cannot access the company, whether it exists or not) or `api_company_param_conflict` (a `companyId` query parameter that does not match the path).","headers":{"WWW-Authenticate":{"description":"Only with `api_insufficient_scope`.","schema":{"type":"string"}},"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseWithCode"}}}},"429":{"description":"`api_rate_limited`: too many requests for the account or the client. Retry after `Retry-After` seconds.","headers":{"Retry-After":{"$ref":"#/components/headers/RetryAfter"},"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseWithCode"},"examples":{"api_rate_limited":{"$ref":"#/components/examples/PublicApiRateLimited"}}}}},"500":{"description":"`internal_server_error`: unexpected failure. Safe to retry with backoff.","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseWithCode"}}}},"503":{"description":"`public_api_disabled` (the public API is not enabled for the client's account, during the invitation-only preview, or has been switched off) or `api_temporarily_unavailable` (a dependency failed). Retry `api_temporarily_unavailable` after `Retry-After` seconds. Do not retry `public_api_disabled` at that pace: back off exponentially, with at most 5 minutes between attempts, or stop and contact Fibady quoting `X-Request-Id`.","headers":{"Retry-After":{"$ref":"#/components/headers/RetryAfter"},"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseWithCode"}}}}},"security":[{"oauth2":["banking:read"]}],"parameters":[{"name":"companyId","in":"path","required":true,"description":"Company whose bank movements are listed. It must be one the client can access.","schema":{"type":"string"}},{"name":"limit","in":"query","required":false,"description":"Page size, 1 to 500. Defaults to 500.","schema":{"type":"integer","minimum":1,"maximum":500,"default":500}},{"name":"cursor","in":"query","required":false,"description":"Opaque `nextCursor` from the previous page of the same list and company. With it, `updatedSince` may be omitted; if sent, it must be the value the pass started with.","schema":{"type":"string","maxLength":2048}},{"name":"updatedSince","in":"query","required":false,"description":"Only records changed at or after this instant: an RFC 3339 date-time with a time zone offset (`Z` or `±HH:MM`) and up to 6 fractional digits, such as `2026-10-10T08:15:30.123456Z`. Percent-encode `+` as `%2B`. A future instant gives an empty last page.","schema":{"type":"string","format":"date-time"}}],"x-codeSamples":[{"lang":"Shell","label":"curl","source":": \"${FIBADY_ACCESS_TOKEN:?set it to the access_token of POST /v1/oauth/token, or to the output of fibady_token after the Quickstart}\" &&\nprintf 'header = \"Authorization: Bearer %s\"\\n' \"$FIBADY_ACCESS_TOKEN\" \\\n  | curl -sS -K - 'https://public-api.fibady.com/v1/companies/01K8EXAMP0C0MPANY000000001/bank-movements'"}]}},"/v1/companies/{companyId}/bank-movements/{externalId}":{"get":{"operationId":"getBankMovement","summary":"Get bank movement state","description":"Whether a bank movement written to this company with `POST /v1/companies/{companyId}/bank-movements/batch` is in Fibady, looked up by the `externalId` it was written with: `projected`, with its `internalId`, or `pending`. It returns nothing else of the record. Use it to confirm a record after a write whose result you could not read, before sending it again. Any client of the account with access to the company can read it, not only the one that wrote it.\n\nOnly records written through the API are found: a bank movement that another source loaded (manual entry, a CSV import, an ERP connector) gives 404 even if it carries the same identifier. An `externalId` that does not exist, belongs to another company, belongs to a record of another kind (an invoice's `externalId` asked for as a bank movement) or is malformed gives the same 404.\n\nPercent-encode the `externalId` in the path.\n\nAsk with the `externalId` exactly as it was written, with no prefix: the response returns it the same way.\n\nEvery response carries `X-Request-Id`, `Cache-Control: no-store` and `Pragma: no-cache`. Only the 429 and the 503 carry `Retry-After`. Error messages follow `Accept-Language` (English by default).","tags":["Bank movements"],"responses":{"200":{"description":"The state of the record.","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicApiRecordStatus"}}}},"400":{"description":"`api_invalid_query`: the operation takes no query parameters, and one was sent.","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseWithCode"}}}},"401":{"description":"`api_token_missing` (no `Authorization: Bearer` header) or `api_token_invalid` (unknown, expired or revoked access credential, or an inactive client). Carries `WWW-Authenticate`. Request a new token.","headers":{"WWW-Authenticate":{"schema":{"type":"string"}},"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseWithCode"}}}},"402":{"description":"`auth_invalid_or_expired_license`: the account's license is not valid.","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseWithCode"}}}},"403":{"description":"`api_origin_forbidden` (the request did not come through the public API endpoint), `api_insufficient_scope` (the client lacks `banking:read`; carries `WWW-Authenticate`), `api_company_not_allowed` (the client cannot access the company, whether it exists or not) or `api_company_param_conflict` (a `companyId` query parameter that does not match the path).","headers":{"WWW-Authenticate":{"description":"Only with `api_insufficient_scope`.","schema":{"type":"string"}},"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseWithCode"}}}},"404":{"description":"`api_resource_not_found`: there is no such bank movement written through the API for this company. The same response for an `externalId` that does not exist, belongs to another company, belongs to a record of another kind or from another source, or is malformed.","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseWithCode"}}}},"429":{"description":"`api_rate_limited`: too many requests for the account or the client. Retry after `Retry-After` seconds.","headers":{"Retry-After":{"$ref":"#/components/headers/RetryAfter"},"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseWithCode"},"examples":{"api_rate_limited":{"$ref":"#/components/examples/PublicApiRateLimited"}}}}},"500":{"description":"`internal_server_error`: unexpected failure. Safe to retry with backoff.","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseWithCode"}}}},"503":{"description":"`public_api_disabled` (the public API is not enabled for the client's account, during the invitation-only preview, or has been switched off) or `api_temporarily_unavailable` (a dependency failed). Retry `api_temporarily_unavailable` after `Retry-After` seconds. Do not retry `public_api_disabled` at that pace: back off exponentially, with at most 5 minutes between attempts, or stop and contact Fibady quoting `X-Request-Id`.","headers":{"Retry-After":{"$ref":"#/components/headers/RetryAfter"},"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseWithCode"}}}}},"security":[{"oauth2":["banking:read"]}],"parameters":[{"name":"companyId","in":"path","required":true,"description":"Company the record was written to. It must be one the client can access.","schema":{"type":"string"}},{"name":"externalId","in":"path","required":true,"description":"The `externalId` the bank movement was written with, percent-encoded: 1 to 255 characters, without control characters and without leading or trailing whitespace.","schema":{"type":"string","minLength":1,"maxLength":255}}],"x-codeSamples":[{"lang":"Shell","label":"curl","source":": \"${FIBADY_ACCESS_TOKEN:?set it to the access_token of POST /v1/oauth/token, or to the output of fibady_token after the Quickstart}\" &&\nprintf 'header = \"Authorization: Bearer %s\"\\n' \"$FIBADY_ACCESS_TOKEN\" \\\n  | curl -sS -K - 'https://public-api.fibady.com/v1/companies/01K8EXAMP0C0MPANY000000001/bank-movements/example-movement-0001'"}]}},"/v1/companies/{companyId}/bank-movements/batch":{"post":{"operationId":"upsertBankMovements","summary":"Upsert bank movements","description":"Upserts up to 500 bank movements of the company. A movement is identified by its `externalId`, which is scoped to the company of the path, not to the account: the same `externalId` sent to two companies creates two different movements. `results[].externalId` is your identifier as sent. Every upsert replaces the WHOLE movement: send it complete each time; an optional field that is omitted is cleared.\n\n`bankAccountExternalId` must be a bank account loaded through `bank-accounts/batch` into this same company; any other account, created in Fibady, imported from a file or synced from an ERP connector, is a 422 (`unknown_reference`). Closed accounts are accepted. If the account is removed between the validation and the load, that movement comes back `unprojectable` with code `missing_parent` and the rest of the batch is loaded. A movement keeps the account it was first loaded into: sending it again with another `bankAccountExternalId` is a 422 (`bank_movement_account_change`).\n\nThere is no `currency` field: every movement is stored in the currency of its account, and sending `currency` is a 422 (`unknown_field`). `amount` carries the direction in its sign: positive is money in, negative is money out. Movements from the API never match or change movements that came from a file or from Fibady, even with the same identifier.\n\nAfter the batch, the company's bank rules classify its new movements, also by `counterpartyName` and `counterpartyAccount`. That runs after the response (see `postProcessing`) and does not reclassify a movement that is already classified.\n\nWarnings, which do not block the movement: `date_in_closed_period` (its date, or its stored date, falls in a closed month), `date_far_future` (more than 24 months ahead), `fx_rate_missing` (its account is in a currency other than the company's and there is no closing exchange rate in Fibady to convert it, on any date, directly or through USD: until that rate is loaded in Fibady the amount counts unconverted in the reports and is flagged in the company's data status. Once the rate is loaded, the movement is converted automatically if it is dated in the month the rate is loaded or later, or in the three months before it, or if the Fibady account uses its own exchange rates; an older movement may stay unconverted, and sending it again unchanged does not convert it, since it comes back `unchanged`) and `before_opening_balance_date` (its date is on or before the opening balance date of its account: the opening balance already includes it, so balances would count it twice; set the opening balance date before the first movement you send).\n\nAllowed only when the company's bank data is fed by the API; otherwise 409 `source_not_owned`. It does not depend on where journal entries and invoices come from.\n\nValidation is atomic: if any record is invalid, the response is 422 and nothing is written. The load itself is not atomic; after a 5xx or a timeout, send the same batch again: records already loaded come back as `unchanged`. `reportsVisibility` may stay `pending` for a long time, and that is not a failure of the load: the reports of a large company are refreshed as a whole, not by month. Do not wait for `visible` to consider a batch loaded.\n\nSend an `Idempotency-Key` to make a retry return the stored response instead of loading again. With `dryRun=true` the batch is only validated, with the same warnings, and nothing is written.\n\nEvery response carries `X-Request-Id`, `Cache-Control: no-store` and `Pragma: no-cache`. Error messages follow `Accept-Language` (English by default).","tags":["Bank movements"],"responses":{"200":{"description":"The batch was loaded or, with `dryRun=true`, validated.","headers":{"Idempotent-Replayed":{"description":"Present, with `true`, only when the response is the replay of an earlier write with the same `Idempotency-Key` and the same body. The body of a replay is the stored one, except `reportsVisibility` and `postProcessing`, which are computed again from the run.","schema":{"type":"string"}},"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicApiBatchResult"}}}},"400":{"description":"`api_invalid_query` (a query parameter other than `dryRun`, a repeated one, or a `dryRun` that is not `true` or `false`) or `api_invalid_json` (the body is not valid JSON).","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseWithCode"}}}},"401":{"description":"`api_token_missing` (no `Authorization: Bearer` header) or `api_token_invalid` (unknown, expired or revoked access credential, or an inactive client). Carries `WWW-Authenticate`. Request a new token.","headers":{"WWW-Authenticate":{"schema":{"type":"string"}},"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseWithCode"}}}},"402":{"description":"`auth_invalid_or_expired_license`: the account's license is not valid.","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseWithCode"}}}},"403":{"description":"`api_origin_forbidden` (the request did not come through the public API endpoint), `api_insufficient_scope` (the client lacks `banking:write`; carries `WWW-Authenticate`), `api_company_not_allowed` (the client cannot access the company, whether it exists or not) or `api_company_param_conflict` (a `companyId` in the query or in the body that does not match the path: the company is always the one of the path).","headers":{"WWW-Authenticate":{"description":"Only with `api_insufficient_scope`.","schema":{"type":"string"}},"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseWithCode"}}}},"409":{"description":"The batch cannot be loaded now, and nothing was written. `source_not_owned`: the company's bank data is not fed by the API; `details.reason` is `source_not_assigned` (the company has no bank source) or `source_not_api` (it has one, and none is the API). `batch_in_progress`: another batch of the company is loading; no `details`. `idempotency_key_in_progress`: a write with the same `Idempotency-Key` is still running; no `details`. The last two carry `Retry-After`.","headers":{"Retry-After":{"description":"Only with `batch_in_progress` and `idempotency_key_in_progress`.","schema":{"type":"integer","minimum":1}},"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicApiErrorWithDetails"},"examples":{"batch_in_progress":{"$ref":"#/components/examples/PublicApiBatchInProgress"}}}}},"413":{"description":"`api_payload_too_large` (the body is larger than 1 MiB) or `api_batch_too_large` (more than 500 movements).","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseWithCode"}}}},"422":{"description":"`validation_failed`: some record is invalid, `details` says where, and nothing was written. `idempotency_key_reused`: the `Idempotency-Key` was already used with a different body; no `details`.","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicApiValidationError"},"examples":{"validation_failed":{"$ref":"#/components/examples/PublicApiValidationFailed"}}}}},"429":{"description":"`api_rate_limited` (too many requests for the account or the client), `api_quota_exceeded` (the account's daily write quota is used up) or `api_concurrent_requests_exceeded` (too many writes of the account in flight). Retry after `Retry-After` seconds.","headers":{"Retry-After":{"$ref":"#/components/headers/RetryAfter"},"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseWithCode"},"examples":{"api_rate_limited":{"$ref":"#/components/examples/PublicApiRateLimited"}}}}},"500":{"description":"`internal_server_error`: unexpected failure. The batch may be partially loaded; send it again.","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseWithCode"}}}},"503":{"description":"`public_api_disabled` (the public API is not enabled for the client's account, during the invitation-only preview, or has been switched off; nothing was written) or `api_temporarily_unavailable` (a dependency failed, or there was not enough time left to start the load). After `api_temporarily_unavailable` the batch may be partially loaded: send it again after `Retry-After` seconds. Do not retry `public_api_disabled` at that pace: back off exponentially, with at most 5 minutes between attempts, or stop and contact Fibady quoting `X-Request-Id`.","headers":{"Retry-After":{"$ref":"#/components/headers/RetryAfter"},"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseWithCode"}}}}},"security":[{"oauth2":["banking:write"]}],"parameters":[{"name":"companyId","in":"path","required":true,"description":"Company whose bank movements are written. It must be one the client can access.","schema":{"type":"string"}},{"name":"dryRun","in":"query","required":false,"description":"`true` validates the batch and writes nothing: no run, no quota. Only `true` or `false`; any other value, or any other query parameter, is a 400.","schema":{"type":"boolean"}},{"name":"Idempotency-Key","in":"header","required":false,"description":"Makes the write idempotent: the same key with the same body returns the stored response, with `Idempotent-Replayed: true`. 1 to 255 printable ASCII characters, no spaces. Ignored with `dryRun=true`.","schema":{"type":"string","pattern":"^[\\x21-\\x7E]{1,255}$"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"properties":{"items":{"type":"array","minItems":1,"maxItems":500,"description":"The movements. An `externalId` may appear only once in the batch.","items":{"type":"object","additionalProperties":false,"properties":{"externalId":{"type":"string","minLength":1,"maxLength":255,"description":"Your identifier of the movement: its identity within the company, not within the account. No control characters and no leading or trailing whitespace."},"bankAccountExternalId":{"type":"string","minLength":1,"maxLength":255,"description":"`externalId` of a bank account loaded through `bank-accounts/batch` into this company. It cannot change once the movement exists."},"date":{"type":"string","format":"date","description":"Date of the movement, `YYYY-MM-DD`."},"amount":{"type":"string","pattern":"^-?\\d{1,13}(\\.\\d{1,2})?$","description":"Amount in the currency of the account, as a decimal string with sign: positive is money in, negative is money out. Up to 13 integer digits and up to 2 decimals, with a dot (`\"1500.25\"`, `\"-320.00\"`). A JSON number is a 422."},"description":{"type":["string","null"],"minLength":1,"pattern":"\\S","description":"Concept of the movement. It cannot be empty or blank; omit it or send `null` to leave it out."},"counterpartyName":{"type":["string","null"],"minLength":1,"pattern":"\\S","description":"Name of the other party of the movement, as the bank shows it. Bank rules can match it. It cannot be empty or blank; omit it or send `null` to leave it out."},"counterpartyAccount":{"type":["string","null"],"minLength":1,"pattern":"\\S","description":"Account of the other party (an IBAN or an account number), stored as sent. Bank rules can match it. It cannot be empty or blank; omit it or send `null` to leave it out."}},"required":["externalId","bankAccountExternalId","date","amount"]}}},"required":["items"]},"examples":{"payment_and_collection":{"summary":"A payment out and a collection in","value":{"items":[{"externalId":"example-movement-0001","bankAccountExternalId":"example-bank-account-0001","date":"2026-10-01","amount":"-800.00","description":"Office rent, September","counterpartyName":"Example Supplies GmbH","counterpartyAccount":"DE89370400440532013000"},{"externalId":"example-movement-0002","bankAccountExternalId":"example-bank-account-0001","date":"2026-10-05","amount":"1210.00","description":"Invoice 2026-0001","counterpartyName":"Example Customer"}]}}}}}},"x-codeSamples":[{"lang":"Shell","label":"curl","source":": \"${FIBADY_ACCESS_TOKEN:?set it to the access_token of POST /v1/oauth/token, or to the output of fibady_token after the Quickstart}\" &&\nprintf 'header = \"Authorization: Bearer %s\"\\n' \"$FIBADY_ACCESS_TOKEN\" \\\n  | curl -sS -K - 'https://public-api.fibady.com/v1/companies/01K8EXAMP0C0MPANY000000001/bank-movements/batch' \\\n    -H 'Content-Type: application/json' \\\n    -H 'Idempotency-Key: example-bank-movements-batch-0001' \\\n    --data-binary '{\n      \"items\": [\n        {\n          \"externalId\": \"example-movement-0001\",\n          \"bankAccountExternalId\": \"example-bank-account-0001\",\n          \"date\": \"2026-10-01\",\n          \"amount\": \"-800.00\",\n          \"description\": \"Office rent, September\",\n          \"counterpartyName\": \"Example Supplies GmbH\",\n          \"counterpartyAccount\": \"DE89370400440532013000\"\n        },\n        {\n          \"externalId\": \"example-movement-0002\",\n          \"bankAccountExternalId\": \"example-bank-account-0001\",\n          \"date\": \"2026-10-05\",\n          \"amount\": \"1210.00\",\n          \"description\": \"Invoice 2026-0001\",\n          \"counterpartyName\": \"Example Customer\"\n        }\n      ]\n    }'"}]}},"/v1/companies/{companyId}/bank-movements/batch-delete":{"post":{"operationId":"deleteBankMovements","summary":"Delete bank movements","description":"Deletes up to 500 bank movements of the company, by the `externalId` they were written with through `bank-movements/batch`. Only movements written through the API into the company of the path are deleted; a movement from any other source (imported from a file, created in Fibady, synced from a connector) is never touched, even with the same identifier.\n\nEach requested `externalId` comes back `deleted`, or `not_found` when the company has no live movement with that `externalId` written through the API (it never existed, it was already deleted, its bank account was deleted in Fibady, or it belongs to another company): nothing tells those cases apart. A movement that was accepted but never written (`unprojectable`) is deleted too, with `internalId: null`. After a deletion, `GET` of that `externalId` is a 404, and repeating the deletion returns `not_found`.\n\nConfirmation: when the company has at least 10 live bank movements written through the API and the batch would delete more than 30% of them, send `expectedDeletes` with the number of items of the batch; without it the response is 409 `delete_confirmation_required`, with `details.candidateCount` (movements that would be deleted) and `details.universeSize` (live movements), and nothing is deleted. When sent, `expectedDeletes` must be the number of items of the batch, even if no confirmation is needed; otherwise the response is 422 (`expected_deletes_mismatch`). Use `dryRun=true` to see the outcome first: it answers the same 409.\n\nDeleting a record dated in a month up to `lastClosedMonth` is not blocked: it carries the warning `date_in_closed_period` and recalculates the budget variance and the forecast of that month.\n\nAllowed only when the company's bank data is fed by the API; otherwise 409 `source_not_owned`. It does not depend on where journal entries and invoices come from.\n\nValidation is atomic: if any item is invalid, the response is 422 and nothing is deleted. The deletion itself is not atomic: after a 5xx or a timeout, send the same deletion again BEFORE sending any of those movements through `bank-movements/batch`; a repeated deletion completes it. `reportsVisibility` may stay `pending` for a long time, and that is not a failure of the deletion: the reports of a large company are refreshed as a whole, not by month. Do not wait for `visible` to consider a deletion done.\n\nSending a deleted `externalId` again through `bank-movements/batch` creates the movement again (`created`), always with a new `internalId`.\n\nSend an `Idempotency-Key` to make a retry return the stored response instead of deleting again; a key already used with another body, or with `bank-movements/batch`, is a 422 (`idempotency_key_reused`). With `dryRun=true` the batch is only checked, with the same warnings, and nothing is deleted.\n\nEvery response carries `X-Request-Id`, `Cache-Control: no-store` and `Pragma: no-cache`. Error messages follow `Accept-Language` (English by default).","tags":["Bank movements"],"responses":{"200":{"description":"The movements were deleted or, with `dryRun=true`, checked.","headers":{"Idempotent-Replayed":{"description":"Present, with `true`, only when the response is the replay of an earlier deletion with the same `Idempotency-Key` and the same body. The body of a replay is the stored one, except `reportsVisibility` and `postProcessing`, which are computed again from the run.","schema":{"type":"string"}},"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/PublicApiDeleteBatchResult"},{"$ref":"#/components/schemas/PublicApiDeleteDryRunResult"}]}}}},"400":{"description":"`api_invalid_query` (a query parameter other than `dryRun`, a repeated one, or a `dryRun` that is not `true` or `false`) or `api_invalid_json` (the body is not valid JSON).","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseWithCode"}}}},"401":{"description":"`api_token_missing` (no `Authorization: Bearer` header) or `api_token_invalid` (unknown, expired or revoked access credential, or an inactive client). Carries `WWW-Authenticate`. Request a new token.","headers":{"WWW-Authenticate":{"schema":{"type":"string"}},"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseWithCode"}}}},"402":{"description":"`auth_invalid_or_expired_license`: the account's license is not valid.","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseWithCode"}}}},"403":{"description":"`api_origin_forbidden` (the request did not come through the public API endpoint), `api_insufficient_scope` (the client lacks `banking:delete`, which `banking:write` does not include; carries `WWW-Authenticate`), `api_company_not_allowed` (the client cannot access the company, whether it exists or not) or `api_company_param_conflict` (a `companyId` in the query or in the body that does not match the path: the company is always the one of the path).","headers":{"WWW-Authenticate":{"description":"Only with `api_insufficient_scope`.","schema":{"type":"string"}},"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseWithCode"}}}},"409":{"description":"Nothing was deleted. `source_not_owned`: the company's bank data is not fed by the API; `details.reason` is `source_not_assigned` (the company has no bank source) or `source_not_api` (it has one, and none is the API). `delete_confirmation_required`: the batch would delete a large share of the company's bank movements and `expectedDeletes` was not sent; `details.candidateCount` is the number of movements that would be deleted and `details.universeSize` the number of live movements; send the batch again with `expectedDeletes`. `batch_in_progress`: another batch of the company is running; no `details`. `idempotency_key_in_progress`: a request with the same `Idempotency-Key` is still running; no `details`. The last two carry `Retry-After`.","headers":{"Retry-After":{"description":"Only with `batch_in_progress` and `idempotency_key_in_progress`.","schema":{"type":"integer","minimum":1}},"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicApiErrorWithDetails"},"examples":{"batch_in_progress":{"$ref":"#/components/examples/PublicApiBatchInProgress"}}}}},"413":{"description":"`api_payload_too_large` (the body is larger than 1 MiB) or `api_batch_too_large` (more than 500 items).","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseWithCode"}}}},"422":{"description":"`validation_failed`: some item is invalid, `details` says where, and nothing was deleted. `idempotency_key_reused`: the `Idempotency-Key` was already used with a different body or operation; no `details`.","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicApiValidationError"},"examples":{"validation_failed":{"$ref":"#/components/examples/PublicApiValidationFailed"}}}}},"429":{"description":"`api_rate_limited` (too many requests for the account or the client), `api_quota_exceeded` (the account's daily write quota is used up) or `api_concurrent_requests_exceeded` (too many writes of the account in flight). Retry after `Retry-After` seconds.","headers":{"Retry-After":{"$ref":"#/components/headers/RetryAfter"},"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseWithCode"},"examples":{"api_rate_limited":{"$ref":"#/components/examples/PublicApiRateLimited"}}}}},"500":{"description":"`internal_server_error`: unexpected failure. The deletion may be partially applied; send the same deletion again.","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseWithCode"}}}},"503":{"description":"`public_api_disabled` (the public API is not enabled for the client's account, during the invitation-only preview, or has been switched off; nothing was deleted) or `api_temporarily_unavailable` (a dependency failed, or there was not enough time left to start the deletion). After `api_temporarily_unavailable` the deletion may be partially applied: send the same deletion again after `Retry-After` seconds. Do not retry `public_api_disabled` at that pace: back off exponentially, with at most 5 minutes between attempts, or stop and contact Fibady quoting `X-Request-Id`.","headers":{"Retry-After":{"$ref":"#/components/headers/RetryAfter"},"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseWithCode"}}}}},"security":[{"oauth2":["banking:delete"]}],"parameters":[{"name":"companyId","in":"path","required":true,"description":"Company whose bank movements are deleted. It must be one the client can access.","schema":{"type":"string"}},{"name":"dryRun","in":"query","required":false,"description":"`true` checks the batch and deletes nothing: no run, no quota. Only `true` or `false`; any other value, or any other query parameter, is a 400.","schema":{"type":"boolean"}},{"name":"Idempotency-Key","in":"header","required":false,"description":"Makes the deletion idempotent: the same key with the same body returns the stored response, with `Idempotent-Replayed: true`. 1 to 255 printable ASCII characters, no spaces. Ignored with `dryRun=true`.","schema":{"type":"string","pattern":"^[\\x21-\\x7E]{1,255}$"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"properties":{"items":{"type":"array","minItems":1,"maxItems":500,"description":"The bank movements to delete. An `externalId` may appear only once in the batch.","items":{"type":"object","additionalProperties":false,"properties":{"externalId":{"type":"string","minLength":1,"maxLength":255,"description":"Your identifier of the movement, as sent to `bank-movements/batch`. No control characters and no leading or trailing whitespace."}},"required":["externalId"]}},"expectedDeletes":{"type":["integer","null"],"description":"The number of items of the batch, to confirm a large deletion. Omitted or `null` means no confirmation. When sent, it must be exactly the number of items."}},"required":["items"]},"examples":{"one_movement":{"summary":"One bank movement, confirming how many are deleted","value":{"items":[{"externalId":"example-movement-0001"}],"expectedDeletes":1}}}}}},"x-codeSamples":[{"lang":"Shell","label":"curl","source":": \"${FIBADY_ACCESS_TOKEN:?set it to the access_token of POST /v1/oauth/token, or to the output of fibady_token after the Quickstart}\" &&\nprintf 'header = \"Authorization: Bearer %s\"\\n' \"$FIBADY_ACCESS_TOKEN\" \\\n  | curl -sS -K - 'https://public-api.fibady.com/v1/companies/01K8EXAMP0C0MPANY000000001/bank-movements/batch-delete' \\\n    -H 'Content-Type: application/json' \\\n    -H 'Idempotency-Key: example-bank-movements-batch-delete-0001' \\\n    --data-binary '{\n      \"items\": [\n        {\n          \"externalId\": \"example-movement-0001\"\n        }\n      ],\n      \"expectedDeletes\": 1\n    }'"}]}},"/v1/companies/{companyId}/invoice-payments":{"get":{"operationId":"listInvoicePayments","summary":"List invoice payments","description":"The invoice payments written to this company with `POST /v1/companies/{companyId}/invoice-payments/batch`, one summary per record: its `externalId`, `projected` (with its `internalId`) or `pending`, and `updatedAt`. It returns nothing else of the record. Use it to reconcile what Fibady holds with your own system. Any client of the account with access to the company can list them, not only the one that wrote them.\n\nOnly records written through the API are listed: an invoice payment that another source loaded (manual entry, a CSV import, an ERP connector) is not. Deleted records are not listed either, and neither is a payment that went away with its invoice (the invoice was deleted, through the API or in Fibady): if the invoice is sent again, send its payments again and they are listed again, with the same `internalId`. A page may come out shorter, or empty, because of those payments.\n\nRecords come in an internal order, not by `externalId`. Paginate with `cursor` until `nextCursor` is `null`; a page may be shorter than `limit`, even empty, with a non-null `nextCursor`.\n\nFor an incremental sync, pass `updatedSince` to get only the records changed since then. Every page of a pass returns the same `asOf`: start the next pass with `updatedSince` set to it, and no change in between is missed (a record may appear in two passes). `updatedSince` needs a time zone offset: send `Z`, or percent-encode the `+` of a positive offset as `%2B` (an unencoded `+` reads as a space and gives 400).\n\nEvery response carries `X-Request-Id`, `Cache-Control: no-store` and `Pragma: no-cache`. Only the 429 and the 503 carry `Retry-After`. Error messages follow `Accept-Language` (English by default).","tags":["Invoice payments"],"responses":{"200":{"description":"One page of invoice payments.","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicApiRecordPage"}}}},"400":{"description":"`api_invalid_query`: an unknown or repeated query parameter, a `limit` that is not an integer from 1 to 500, an `updatedSince` that is not an RFC 3339 date-time with a time zone offset, or a `cursor` that is malformed, comes from another list or company, or is sent with another `updatedSince`.","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseWithCode"}}}},"401":{"description":"`api_token_missing` (no `Authorization: Bearer` header) or `api_token_invalid` (unknown, expired or revoked access credential, or an inactive client). Carries `WWW-Authenticate`. Request a new token.","headers":{"WWW-Authenticate":{"schema":{"type":"string"}},"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseWithCode"}}}},"402":{"description":"`auth_invalid_or_expired_license`: the account's license is not valid.","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseWithCode"}}}},"403":{"description":"`api_origin_forbidden` (the request did not come through the public API endpoint), `api_insufficient_scope` (the client lacks `invoicing:read`; carries `WWW-Authenticate`), `api_company_not_allowed` (the client cannot access the company, whether it exists or not) or `api_company_param_conflict` (a `companyId` query parameter that does not match the path).","headers":{"WWW-Authenticate":{"description":"Only with `api_insufficient_scope`.","schema":{"type":"string"}},"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseWithCode"}}}},"429":{"description":"`api_rate_limited`: too many requests for the account or the client. Retry after `Retry-After` seconds.","headers":{"Retry-After":{"$ref":"#/components/headers/RetryAfter"},"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseWithCode"},"examples":{"api_rate_limited":{"$ref":"#/components/examples/PublicApiRateLimited"}}}}},"500":{"description":"`internal_server_error`: unexpected failure. Safe to retry with backoff.","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseWithCode"}}}},"503":{"description":"`public_api_disabled` (the public API is not enabled for the client's account, during the invitation-only preview, or has been switched off) or `api_temporarily_unavailable` (a dependency failed). Retry `api_temporarily_unavailable` after `Retry-After` seconds. Do not retry `public_api_disabled` at that pace: back off exponentially, with at most 5 minutes between attempts, or stop and contact Fibady quoting `X-Request-Id`.","headers":{"Retry-After":{"$ref":"#/components/headers/RetryAfter"},"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseWithCode"}}}}},"security":[{"oauth2":["invoicing:read"]}],"parameters":[{"name":"companyId","in":"path","required":true,"description":"Company whose invoice payments are listed. It must be one the client can access.","schema":{"type":"string"}},{"name":"limit","in":"query","required":false,"description":"Page size, 1 to 500. Defaults to 500.","schema":{"type":"integer","minimum":1,"maximum":500,"default":500}},{"name":"cursor","in":"query","required":false,"description":"Opaque `nextCursor` from the previous page of the same list and company. With it, `updatedSince` may be omitted; if sent, it must be the value the pass started with.","schema":{"type":"string","maxLength":2048}},{"name":"updatedSince","in":"query","required":false,"description":"Only records changed at or after this instant: an RFC 3339 date-time with a time zone offset (`Z` or `±HH:MM`) and up to 6 fractional digits, such as `2026-10-10T08:15:30.123456Z`. Percent-encode `+` as `%2B`. A future instant gives an empty last page.","schema":{"type":"string","format":"date-time"}}],"x-codeSamples":[{"lang":"Shell","label":"curl","source":": \"${FIBADY_ACCESS_TOKEN:?set it to the access_token of POST /v1/oauth/token, or to the output of fibady_token after the Quickstart}\" &&\nprintf 'header = \"Authorization: Bearer %s\"\\n' \"$FIBADY_ACCESS_TOKEN\" \\\n  | curl -sS -K - 'https://public-api.fibady.com/v1/companies/01K8EXAMP0C0MPANY000000001/invoice-payments'"}]}},"/v1/companies/{companyId}/invoice-payments/{externalId}":{"get":{"operationId":"getInvoicePayment","summary":"Get invoice payment state","description":"Whether an invoice payment written to this company with `POST /v1/companies/{companyId}/invoice-payments/batch` is in Fibady, looked up by the `externalId` it was written with: `projected`, with its `internalId`, or `pending`. It returns nothing else of the record: no amount and no date. Use it to confirm a record after a write whose result you could not read, before sending it again. Any client of the account with access to the company can read it, not only the one that wrote it.\n\nA payment goes away with its invoice: once the invoice is deleted, the payment gives 404, the same as one that never existed, until the invoice is loaded again and the payment is sent again.\n\nOnly records written through the API are found: a payment that another source loaded (an ERP connector, a CSV import, manual entry) gives 404 even if it carries the same identifier. An `externalId` that does not exist, belongs to another company, belongs to a record of another kind (an invoice's `externalId` asked for as a payment) or is malformed gives the same 404.\n\nPercent-encode the `externalId` in the path.\n\nEvery response carries `X-Request-Id`, `Cache-Control: no-store` and `Pragma: no-cache`. Only the 429 and the 503 carry `Retry-After`. Error messages follow `Accept-Language` (English by default).","tags":["Invoice payments"],"responses":{"200":{"description":"The state of the record.","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicApiRecordStatus"}}}},"400":{"description":"`api_invalid_query`: the operation takes no query parameters, and one was sent.","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseWithCode"}}}},"401":{"description":"`api_token_missing` (no `Authorization: Bearer` header) or `api_token_invalid` (unknown, expired or revoked access credential, or an inactive client). Carries `WWW-Authenticate`. Request a new token.","headers":{"WWW-Authenticate":{"schema":{"type":"string"}},"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseWithCode"}}}},"402":{"description":"`auth_invalid_or_expired_license`: the account's license is not valid.","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseWithCode"}}}},"403":{"description":"`api_origin_forbidden` (the request did not come through the public API endpoint), `api_insufficient_scope` (the client lacks `invoicing:read`; carries `WWW-Authenticate`), `api_company_not_allowed` (the client cannot access the company, whether it exists or not) or `api_company_param_conflict` (a `companyId` query parameter that does not match the path).","headers":{"WWW-Authenticate":{"description":"Only with `api_insufficient_scope`.","schema":{"type":"string"}},"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseWithCode"}}}},"404":{"description":"`api_resource_not_found`: there is no such invoice payment written through the API for this company, or it went away with its deleted invoice. The same response for an `externalId` that does not exist, belongs to another company, belongs to a record of another kind or from another source, or is malformed.","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseWithCode"}}}},"429":{"description":"`api_rate_limited`: too many requests for the account or the client. Retry after `Retry-After` seconds.","headers":{"Retry-After":{"$ref":"#/components/headers/RetryAfter"},"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseWithCode"},"examples":{"api_rate_limited":{"$ref":"#/components/examples/PublicApiRateLimited"}}}}},"500":{"description":"`internal_server_error`: unexpected failure. Safe to retry with backoff.","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseWithCode"}}}},"503":{"description":"`public_api_disabled` (the public API is not enabled for the client's account, during the invitation-only preview, or has been switched off) or `api_temporarily_unavailable` (a dependency failed). Retry `api_temporarily_unavailable` after `Retry-After` seconds. Do not retry `public_api_disabled` at that pace: back off exponentially, with at most 5 minutes between attempts, or stop and contact Fibady quoting `X-Request-Id`.","headers":{"Retry-After":{"$ref":"#/components/headers/RetryAfter"},"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseWithCode"}}}}},"security":[{"oauth2":["invoicing:read"]}],"parameters":[{"name":"companyId","in":"path","required":true,"description":"Company the record was written to. It must be one the client can access.","schema":{"type":"string"}},{"name":"externalId","in":"path","required":true,"description":"The `externalId` the invoice payment was written with, percent-encoded: 1 to 255 characters, without control characters and without leading or trailing whitespace.","schema":{"type":"string","minLength":1,"maxLength":255}}],"x-codeSamples":[{"lang":"Shell","label":"curl","source":": \"${FIBADY_ACCESS_TOKEN:?set it to the access_token of POST /v1/oauth/token, or to the output of fibady_token after the Quickstart}\" &&\nprintf 'header = \"Authorization: Bearer %s\"\\n' \"$FIBADY_ACCESS_TOKEN\" \\\n  | curl -sS -K - 'https://public-api.fibady.com/v1/companies/01K8EXAMP0C0MPANY000000001/invoice-payments/example-payment-0001%3Aexample-invoice-2026-0001'"}]}},"/v1/companies/{companyId}/invoice-payments/batch":{"post":{"operationId":"upsertInvoicePayments","summary":"Upsert invoice payments","description":"Upserts up to 500 payment applications of the company. Each record is ONE payment applied to ONE invoice: a payment that settles several invoices is sent as one record per invoice, each with its own `externalId` (for example `<payment>:<invoice>`), repeating the total `amount` of the payment and giving in `dedicatedAmount` the part applied to that invoice. A record is identified by its `externalId`, which is scoped to the company of the path.\n\nThe invoice must be one loaded through `invoices/batch` for this same company, in an earlier request: an invoice loaded any other way, of another company, not loaded yet or deleted is a 422 `unknown_reference`, and a `draft` or `cancelled` invoice is a 422 `invoice_not_payable`. A payment already loaded cannot move to another invoice: that is a 422 `invoice_payment_invoice_change`. Payments cannot be deleted through the API: they go away with their invoice. If the invoice is deleted and later sent again, send its payments again to restore them; they come back with the same `internalId`.\n\n`dedicatedAmount` is in the currency of the invoice. When the payment has the currency of the invoice, `dedicatedAmount` cannot be greater than `amount`; with another currency they are not compared. Overpaying an invoice is accepted.\n\nThe payment status of an invoice is still the one its `status` declares; the payment date of a `paid` invoice is derived from its payments (the latest `paymentDate`), in the same request. A batch of payments changes nothing in the materialized reports, so `reportsVisibility` is normally `visible`; the receivables aging and the payment-days series may take up to 5 minutes to show a new payment.\n\nAllowed only when the company's invoices are fed by the API; otherwise 409. Validation is atomic: if any record is invalid, the response is 422 and nothing is written. The load itself is not atomic; after a 5xx or a timeout, send the same batch again: records already loaded come back as `unchanged`.\n\nSend an `Idempotency-Key` to make a retry return the stored response instead of loading again. With `dryRun=true` the batch is only validated.\n\nEvery response carries `X-Request-Id`, `Cache-Control: no-store` and `Pragma: no-cache`. Error messages follow `Accept-Language` (English by default).","tags":["Invoice payments"],"responses":{"200":{"description":"The batch was loaded or, with `dryRun=true`, validated.","headers":{"Idempotent-Replayed":{"description":"Present, with `true`, only when the response is the replay of an earlier write with the same `Idempotency-Key` and the same body. The body of a replay is the stored one, except `reportsVisibility` and `postProcessing`, which are computed again from the run.","schema":{"type":"string"}},"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicApiBatchResult"}}}},"400":{"description":"`api_invalid_query` (a query parameter other than `dryRun`, a repeated one, or a `dryRun` that is not `true` or `false`) or `api_invalid_json` (the body is not valid JSON).","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseWithCode"}}}},"401":{"description":"`api_token_missing` (no `Authorization: Bearer` header) or `api_token_invalid` (unknown, expired or revoked access credential, or an inactive client). Carries `WWW-Authenticate`. Request a new token.","headers":{"WWW-Authenticate":{"schema":{"type":"string"}},"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseWithCode"}}}},"402":{"description":"`auth_invalid_or_expired_license`: the account's license is not valid.","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseWithCode"}}}},"403":{"description":"`api_origin_forbidden` (the request did not come through the public API endpoint), `api_insufficient_scope` (the client lacks `invoicing:write`; carries `WWW-Authenticate`), `api_company_not_allowed` (the client cannot access the company, whether it exists or not) or `api_company_param_conflict` (a `companyId` in the query or in the body that does not match the path: the company is always the one of the path).","headers":{"WWW-Authenticate":{"description":"Only with `api_insufficient_scope`.","schema":{"type":"string"}},"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseWithCode"}}}},"409":{"description":"The batch cannot be loaded now, and nothing was written. `source_not_owned`: the company's invoices are not fed by the API; `details.reason` is `source_not_assigned` (the company has no invoice source) or `source_not_api` (it has one, and none is the API). `batch_in_progress`: another batch of the company is loading; no `details`. `idempotency_key_in_progress`: a write with the same `Idempotency-Key` is still running; no `details`. The last two carry `Retry-After`.","headers":{"Retry-After":{"description":"Only with `batch_in_progress` and `idempotency_key_in_progress`.","schema":{"type":"integer","minimum":1}},"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicApiErrorWithDetails"},"examples":{"batch_in_progress":{"$ref":"#/components/examples/PublicApiBatchInProgress"}}}}},"413":{"description":"`api_payload_too_large` (the body is larger than 1 MiB) or `api_batch_too_large` (more than 500 records).","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseWithCode"}}}},"422":{"description":"`validation_failed`: some record is invalid, `details` says where, and nothing was written. `idempotency_key_reused`: the `Idempotency-Key` was already used with a different body; no `details`.","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicApiValidationError"},"examples":{"validation_failed":{"$ref":"#/components/examples/PublicApiValidationFailed"}}}}},"429":{"description":"`api_rate_limited` (too many requests for the account or the client), `api_quota_exceeded` (the account's daily write quota is used up) or `api_concurrent_requests_exceeded` (too many writes of the account in flight). Retry after `Retry-After` seconds.","headers":{"Retry-After":{"$ref":"#/components/headers/RetryAfter"},"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseWithCode"},"examples":{"api_rate_limited":{"$ref":"#/components/examples/PublicApiRateLimited"}}}}},"500":{"description":"`internal_server_error`: unexpected failure. The batch may be partially loaded; send it again.","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseWithCode"}}}},"503":{"description":"`public_api_disabled` (the public API is not enabled for the client's account, during the invitation-only preview, or has been switched off; nothing was written) or `api_temporarily_unavailable` (a dependency failed, or there was not enough time left to start the load). After `api_temporarily_unavailable` the batch may be partially loaded: send it again after `Retry-After` seconds. Do not retry `public_api_disabled` at that pace: back off exponentially, with at most 5 minutes between attempts, or stop and contact Fibady quoting `X-Request-Id`.","headers":{"Retry-After":{"$ref":"#/components/headers/RetryAfter"},"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseWithCode"}}}}},"security":[{"oauth2":["invoicing:write"]}],"parameters":[{"name":"companyId","in":"path","required":true,"description":"Company whose invoice payments are written. It must be one the client can access.","schema":{"type":"string"}},{"name":"dryRun","in":"query","required":false,"description":"`true` validates the batch and writes nothing: no run, no quota. Only `true` or `false`; any other value, or any other query parameter, is a 400.","schema":{"type":"boolean"}},{"name":"Idempotency-Key","in":"header","required":false,"description":"Makes the write idempotent: the same key with the same body returns the stored response, with `Idempotent-Replayed: true`. 1 to 255 printable ASCII characters, no spaces. Ignored with `dryRun=true`.","schema":{"type":"string","pattern":"^[\\x21-\\x7E]{1,255}$"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"properties":{"items":{"type":"array","minItems":1,"maxItems":500,"description":"The payment applications, one per payment and invoice. An `externalId` may appear only once in the batch.","items":{"type":"object","additionalProperties":false,"properties":{"externalId":{"type":"string","minLength":1,"maxLength":255,"description":"Your identifier of this application of a payment to an invoice: its identity within the company. No control characters and no leading or trailing whitespace."},"invoiceExternalId":{"type":"string","minLength":1,"maxLength":255,"description":"`externalId` of the invoice, an invoice loaded through `invoices/batch` for this same company. Any other invoice is a 422 `unknown_reference`."},"paymentDate":{"type":"string","format":"date","description":"Date of the payment, `YYYY-MM-DD`. A date-time is a 422. A date in a closed month or more than 24 months ahead is loaded with a warning."},"amount":{"type":"string","pattern":"^\\d{1,13}(\\.\\d{1,2})?$","description":"Total amount of the payment, in `currency`, as a decimal string greater than zero: up to 13 integer digits and up to 2 decimals, with a dot (`\"1500.25\"`). No sign. A JSON number is a 422."},"currency":{"type":"string","pattern":"^[A-Z]{3}$","description":"Currency of the payment, three uppercase letters (ISO 4217, `EUR`)."},"dedicatedAmount":{"type":"string","pattern":"^\\d{1,13}(\\.\\d{1,2})?$","description":"Part of the payment applied to this invoice, in the currency of the INVOICE, with the format of `amount` (greater than zero). With the currency of the invoice, it cannot be greater than `amount`."}},"required":["externalId","invoiceExternalId","paymentDate","amount","currency","dedicatedAmount"]}}},"required":["items"]},"examples":{"full_payment":{"summary":"The full payment of an invoice","value":{"items":[{"externalId":"example-payment-0001:example-invoice-2026-0001","invoiceExternalId":"example-invoice-2026-0001","paymentDate":"2026-10-05","amount":"1210.00","currency":"EUR","dedicatedAmount":"1210.00"}]}}}}}},"x-codeSamples":[{"lang":"Shell","label":"curl","source":": \"${FIBADY_ACCESS_TOKEN:?set it to the access_token of POST /v1/oauth/token, or to the output of fibady_token after the Quickstart}\" &&\nprintf 'header = \"Authorization: Bearer %s\"\\n' \"$FIBADY_ACCESS_TOKEN\" \\\n  | curl -sS -K - 'https://public-api.fibady.com/v1/companies/01K8EXAMP0C0MPANY000000001/invoice-payments/batch' \\\n    -H 'Content-Type: application/json' \\\n    -H 'Idempotency-Key: example-invoice-payments-batch-0001' \\\n    --data-binary '{\n      \"items\": [\n        {\n          \"externalId\": \"example-payment-0001:example-invoice-2026-0001\",\n          \"invoiceExternalId\": \"example-invoice-2026-0001\",\n          \"paymentDate\": \"2026-10-05\",\n          \"amount\": \"1210.00\",\n          \"currency\": \"EUR\",\n          \"dedicatedAmount\": \"1210.00\"\n        }\n      ]\n    }'"}]}},"/v1/companies/{companyId}/invoices":{"get":{"operationId":"listInvoices","summary":"List invoices","description":"The invoices written to this company with `POST /v1/companies/{companyId}/invoices/batch`, one summary per record: its `externalId`, `projected` (with its `internalId`) or `pending`, and `updatedAt`. It returns nothing else of the record. Use it to reconcile what Fibady holds with your own system. Any client of the account with access to the company can list them, not only the one that wrote them.\n\nOnly records written through the API are listed: an invoice that another source loaded (manual entry, a CSV import, an ERP connector) is not. Deleted records are not listed either.\n\nRecords come in an internal order, not by `externalId`. Paginate with `cursor` until `nextCursor` is `null`; a page may be shorter than `limit`, even empty, with a non-null `nextCursor`.\n\nFor an incremental sync, pass `updatedSince` to get only the records changed since then. Every page of a pass returns the same `asOf`: start the next pass with `updatedSince` set to it, and no change in between is missed (a record may appear in two passes). `updatedSince` needs a time zone offset: send `Z`, or percent-encode the `+` of a positive offset as `%2B` (an unencoded `+` reads as a space and gives 400).\n\nEvery response carries `X-Request-Id`, `Cache-Control: no-store` and `Pragma: no-cache`. Only the 429 and the 503 carry `Retry-After`. Error messages follow `Accept-Language` (English by default).","tags":["Invoices"],"responses":{"200":{"description":"One page of invoices.","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicApiRecordPage"}}}},"400":{"description":"`api_invalid_query`: an unknown or repeated query parameter, a `limit` that is not an integer from 1 to 500, an `updatedSince` that is not an RFC 3339 date-time with a time zone offset, or a `cursor` that is malformed, comes from another list or company, or is sent with another `updatedSince`.","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseWithCode"}}}},"401":{"description":"`api_token_missing` (no `Authorization: Bearer` header) or `api_token_invalid` (unknown, expired or revoked access credential, or an inactive client). Carries `WWW-Authenticate`. Request a new token.","headers":{"WWW-Authenticate":{"schema":{"type":"string"}},"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseWithCode"}}}},"402":{"description":"`auth_invalid_or_expired_license`: the account's license is not valid.","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseWithCode"}}}},"403":{"description":"`api_origin_forbidden` (the request did not come through the public API endpoint), `api_insufficient_scope` (the client lacks `invoicing:read`; carries `WWW-Authenticate`), `api_company_not_allowed` (the client cannot access the company, whether it exists or not) or `api_company_param_conflict` (a `companyId` query parameter that does not match the path).","headers":{"WWW-Authenticate":{"description":"Only with `api_insufficient_scope`.","schema":{"type":"string"}},"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseWithCode"}}}},"429":{"description":"`api_rate_limited`: too many requests for the account or the client. Retry after `Retry-After` seconds.","headers":{"Retry-After":{"$ref":"#/components/headers/RetryAfter"},"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseWithCode"},"examples":{"api_rate_limited":{"$ref":"#/components/examples/PublicApiRateLimited"}}}}},"500":{"description":"`internal_server_error`: unexpected failure. Safe to retry with backoff.","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseWithCode"}}}},"503":{"description":"`public_api_disabled` (the public API is not enabled for the client's account, during the invitation-only preview, or has been switched off) or `api_temporarily_unavailable` (a dependency failed). Retry `api_temporarily_unavailable` after `Retry-After` seconds. Do not retry `public_api_disabled` at that pace: back off exponentially, with at most 5 minutes between attempts, or stop and contact Fibady quoting `X-Request-Id`.","headers":{"Retry-After":{"$ref":"#/components/headers/RetryAfter"},"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseWithCode"}}}}},"security":[{"oauth2":["invoicing:read"]}],"parameters":[{"name":"companyId","in":"path","required":true,"description":"Company whose invoices are listed. It must be one the client can access.","schema":{"type":"string"}},{"name":"limit","in":"query","required":false,"description":"Page size, 1 to 500. Defaults to 500.","schema":{"type":"integer","minimum":1,"maximum":500,"default":500}},{"name":"cursor","in":"query","required":false,"description":"Opaque `nextCursor` from the previous page of the same list and company. With it, `updatedSince` may be omitted; if sent, it must be the value the pass started with.","schema":{"type":"string","maxLength":2048}},{"name":"updatedSince","in":"query","required":false,"description":"Only records changed at or after this instant: an RFC 3339 date-time with a time zone offset (`Z` or `±HH:MM`) and up to 6 fractional digits, such as `2026-10-10T08:15:30.123456Z`. Percent-encode `+` as `%2B`. A future instant gives an empty last page.","schema":{"type":"string","format":"date-time"}}],"x-codeSamples":[{"lang":"Shell","label":"curl","source":": \"${FIBADY_ACCESS_TOKEN:?set it to the access_token of POST /v1/oauth/token, or to the output of fibady_token after the Quickstart}\" &&\nprintf 'header = \"Authorization: Bearer %s\"\\n' \"$FIBADY_ACCESS_TOKEN\" \\\n  | curl -sS -K - 'https://public-api.fibady.com/v1/companies/01K8EXAMP0C0MPANY000000001/invoices'"}]}},"/v1/companies/{companyId}/invoices/{externalId}":{"get":{"operationId":"getInvoice","summary":"Get invoice state","description":"Whether an invoice written to this company with `POST /v1/companies/{companyId}/invoices/batch` is in Fibady, looked up by the `externalId` it was written with: `projected`, with its `internalId`, or `pending`. It returns nothing else of the record. Use it to confirm a record after a write whose result you could not read, before sending it again. Any client of the account with access to the company can read it, not only the one that wrote it.\n\nOnly records written through the API are found: an invoice that another source loaded (manual entry, a CSV import, an ERP connector) gives 404 even if it carries the same identifier. An `externalId` that does not exist, belongs to another company, belongs to a record of another kind (a journal entry's `externalId` asked for as an invoice) or is malformed gives the same 404.\n\nPercent-encode the `externalId` in the path.\n\nAsk with the `externalId` exactly as it was written, with no prefix: the response returns it the same way.\n\nEvery response carries `X-Request-Id`, `Cache-Control: no-store` and `Pragma: no-cache`. Only the 429 and the 503 carry `Retry-After`. Error messages follow `Accept-Language` (English by default).","tags":["Invoices"],"responses":{"200":{"description":"The state of the record.","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicApiRecordStatus"}}}},"400":{"description":"`api_invalid_query`: the operation takes no query parameters, and one was sent.","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseWithCode"}}}},"401":{"description":"`api_token_missing` (no `Authorization: Bearer` header) or `api_token_invalid` (unknown, expired or revoked access credential, or an inactive client). Carries `WWW-Authenticate`. Request a new token.","headers":{"WWW-Authenticate":{"schema":{"type":"string"}},"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseWithCode"}}}},"402":{"description":"`auth_invalid_or_expired_license`: the account's license is not valid.","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseWithCode"}}}},"403":{"description":"`api_origin_forbidden` (the request did not come through the public API endpoint), `api_insufficient_scope` (the client lacks `invoicing:read`; carries `WWW-Authenticate`), `api_company_not_allowed` (the client cannot access the company, whether it exists or not) or `api_company_param_conflict` (a `companyId` query parameter that does not match the path).","headers":{"WWW-Authenticate":{"description":"Only with `api_insufficient_scope`.","schema":{"type":"string"}},"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseWithCode"}}}},"404":{"description":"`api_resource_not_found`: there is no such invoice written through the API for this company. The same response for an `externalId` that does not exist, belongs to another company, belongs to a record of another kind or from another source, or is malformed.","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseWithCode"}}}},"429":{"description":"`api_rate_limited`: too many requests for the account or the client. Retry after `Retry-After` seconds.","headers":{"Retry-After":{"$ref":"#/components/headers/RetryAfter"},"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseWithCode"},"examples":{"api_rate_limited":{"$ref":"#/components/examples/PublicApiRateLimited"}}}}},"500":{"description":"`internal_server_error`: unexpected failure. Safe to retry with backoff.","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseWithCode"}}}},"503":{"description":"`public_api_disabled` (the public API is not enabled for the client's account, during the invitation-only preview, or has been switched off) or `api_temporarily_unavailable` (a dependency failed). Retry `api_temporarily_unavailable` after `Retry-After` seconds. Do not retry `public_api_disabled` at that pace: back off exponentially, with at most 5 minutes between attempts, or stop and contact Fibady quoting `X-Request-Id`.","headers":{"Retry-After":{"$ref":"#/components/headers/RetryAfter"},"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseWithCode"}}}}},"security":[{"oauth2":["invoicing:read"]}],"parameters":[{"name":"companyId","in":"path","required":true,"description":"Company the record was written to. It must be one the client can access.","schema":{"type":"string"}},{"name":"externalId","in":"path","required":true,"description":"The `externalId` the invoice was written with, percent-encoded: 1 to 255 characters, without control characters and without leading or trailing whitespace.","schema":{"type":"string","minLength":1,"maxLength":255}}],"x-codeSamples":[{"lang":"Shell","label":"curl","source":": \"${FIBADY_ACCESS_TOKEN:?set it to the access_token of POST /v1/oauth/token, or to the output of fibady_token after the Quickstart}\" &&\nprintf 'header = \"Authorization: Bearer %s\"\\n' \"$FIBADY_ACCESS_TOKEN\" \\\n  | curl -sS -K - 'https://public-api.fibady.com/v1/companies/01K8EXAMP0C0MPANY000000001/invoices/example-invoice-2026-0001'"}]}},"/v1/companies/{companyId}/invoices/batch":{"post":{"operationId":"upsertInvoices","summary":"Upsert invoices","description":"Upserts up to 200 invoices of the company, with up to 2,000 lines in the whole batch. An invoice is identified by its `externalId`, which is scoped to the company of the path. An invoice loaded by anything other than the API (an ERP connector, a CSV import, manual entry) is never matched or changed, even with the same `invoiceNumber` and `invoiceType`: sending it creates another invoice. Sending the `externalId` of an invoice that was deleted in Fibady restores it.\n\nEvery upsert replaces the whole invoice: an optional field that is not sent, or is `null`, is cleared, and the lines are replaced by position (line `i` of the batch is line `i` of the invoice; lines beyond the last one sent are removed). Reordering or removing lines changes their identity. An empty or blank string is not a way to clear a field: it is a 422.\n\nAn invoice in a currency other than the company's is loaded with the warning `fx_rate_missing` when there is no closing exchange rate in Fibady to convert it, on any date, directly or through USD. It does not block the invoice: until that rate is loaded in Fibady its amounts count unconverted in the reports and are flagged in the company's data status. Once the rate is loaded, the invoice is converted automatically if it is dated in the month the rate is loaded or later, or in the three months before it, or if the Fibady account uses its own exchange rates. An older invoice may stay unconverted, and sending it again unchanged does not convert it, since it comes back `unchanged`.\n\nThe payment status of an invoice is derived, never accepted: `paid` invoices are paid and every other `status` is unpaid. The payment date is not part of the invoice: until payments are loaded, a `paid` invoice has no payment date. Header amounts are not checked against the sum of the lines. The sign of a refund is given by `invoiceType`; positive amounts are recommended.\n\nAllowed only when the company's invoices are fed by the API; otherwise 409. Validation is atomic: if any record is invalid, the response is 422 and nothing is written. The load itself is not atomic; after a 5xx or a timeout, send the same batch again: records already loaded come back as `unchanged`, and their payment status is derived again.\n\n`reportsVisibility` may stay `pending` after an invoice batch, until the reports of the whole company are rebuilt, which in a large company may not happen soon. It is not a load failure: do not wait for `visible` to consider a batch loaded.\n\nSend an `Idempotency-Key` to make a retry return the stored response instead of loading again. With `dryRun=true` the batch is only validated.\n\nEvery response carries `X-Request-Id`, `Cache-Control: no-store` and `Pragma: no-cache`. Error messages follow `Accept-Language` (English by default).","tags":["Invoices"],"responses":{"200":{"description":"The batch was loaded or, with `dryRun=true`, validated.","headers":{"Idempotent-Replayed":{"description":"Present, with `true`, only when the response is the replay of an earlier write with the same `Idempotency-Key` and the same body. The body of a replay is the stored one, except `reportsVisibility` and `postProcessing`, which are computed again from the run.","schema":{"type":"string"}},"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicApiBatchResult"}}}},"400":{"description":"`api_invalid_query` (a query parameter other than `dryRun`, a repeated one, or a `dryRun` that is not `true` or `false`) or `api_invalid_json` (the body is not valid JSON).","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseWithCode"}}}},"401":{"description":"`api_token_missing` (no `Authorization: Bearer` header) or `api_token_invalid` (unknown, expired or revoked access credential, or an inactive client). Carries `WWW-Authenticate`. Request a new token.","headers":{"WWW-Authenticate":{"schema":{"type":"string"}},"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseWithCode"}}}},"402":{"description":"`auth_invalid_or_expired_license`: the account's license is not valid.","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseWithCode"}}}},"403":{"description":"`api_origin_forbidden` (the request did not come through the public API endpoint), `api_insufficient_scope` (the client lacks `invoicing:write`; carries `WWW-Authenticate`), `api_company_not_allowed` (the client cannot access the company, whether it exists or not) or `api_company_param_conflict` (a `companyId` in the query or in the body that does not match the path: the company is always the one of the path).","headers":{"WWW-Authenticate":{"description":"Only with `api_insufficient_scope`.","schema":{"type":"string"}},"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseWithCode"}}}},"409":{"description":"The batch cannot be loaded now, and nothing was written. `source_not_owned`: the company's invoices are not fed by the API; `details.reason` is `source_not_assigned` (the company has no invoice source) or `source_not_api` (it has one, and none is the API). `batch_in_progress`: another batch of the company is loading; no `details`. `idempotency_key_in_progress`: a write with the same `Idempotency-Key` is still running; no `details`. The last two carry `Retry-After`.","headers":{"Retry-After":{"description":"Only with `batch_in_progress` and `idempotency_key_in_progress`.","schema":{"type":"integer","minimum":1}},"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicApiErrorWithDetails"},"examples":{"batch_in_progress":{"$ref":"#/components/examples/PublicApiBatchInProgress"}}}}},"413":{"description":"`api_payload_too_large` (the body is larger than 1 MiB) or `api_batch_too_large` (more than 200 invoices, or more than 2,000 lines in the whole batch).","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseWithCode"}}}},"422":{"description":"`validation_failed`: some record is invalid, `details` says where, and nothing was written. `idempotency_key_reused`: the `Idempotency-Key` was already used with a different body; no `details`.","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicApiValidationError"},"examples":{"validation_failed":{"$ref":"#/components/examples/PublicApiValidationFailed"}}}}},"429":{"description":"`api_rate_limited` (too many requests for the account or the client), `api_quota_exceeded` (the account's daily write quota is used up) or `api_concurrent_requests_exceeded` (too many writes of the account in flight). Retry after `Retry-After` seconds.","headers":{"Retry-After":{"$ref":"#/components/headers/RetryAfter"},"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseWithCode"},"examples":{"api_rate_limited":{"$ref":"#/components/examples/PublicApiRateLimited"}}}}},"500":{"description":"`internal_server_error`: unexpected failure. The batch may be partially loaded; send it again.","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseWithCode"}}}},"503":{"description":"`public_api_disabled` (the public API is not enabled for the client's account, during the invitation-only preview, or has been switched off; nothing was written) or `api_temporarily_unavailable` (a dependency failed, or there was not enough time left to start the load). After `api_temporarily_unavailable` the batch may be partially loaded: send it again after `Retry-After` seconds. Do not retry `public_api_disabled` at that pace: back off exponentially, with at most 5 minutes between attempts, or stop and contact Fibady quoting `X-Request-Id`.","headers":{"Retry-After":{"$ref":"#/components/headers/RetryAfter"},"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseWithCode"}}}}},"security":[{"oauth2":["invoicing:write"]}],"parameters":[{"name":"companyId","in":"path","required":true,"description":"Company whose invoices are written. It must be one the client can access.","schema":{"type":"string"}},{"name":"dryRun","in":"query","required":false,"description":"`true` validates the batch and writes nothing: no run, no quota. Only `true` or `false`; any other value, or any other query parameter, is a 400.","schema":{"type":"boolean"}},{"name":"Idempotency-Key","in":"header","required":false,"description":"Makes the write idempotent: the same key with the same body returns the stored response, with `Idempotent-Replayed: true`. 1 to 255 printable ASCII characters, no spaces. Ignored with `dryRun=true`.","schema":{"type":"string","pattern":"^[\\x21-\\x7E]{1,255}$"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"properties":{"items":{"type":"array","minItems":1,"maxItems":200,"description":"The invoices. An `externalId` may appear only once in the batch. The lines of all the invoices of a batch add up to 2,000 at most.","items":{"type":"object","additionalProperties":false,"properties":{"externalId":{"type":"string","minLength":1,"maxLength":255,"description":"Your identifier of the invoice: its identity within the company. No control characters and no leading or trailing whitespace. Send it as is: Fibady never shows it with a prefix."},"invoiceType":{"type":"string","description":"Kind of invoice: issued to a customer, received from a supplier, or a refund of either. Any other value is a 422.","enum":["customer_invoice","supplier_invoice","customer_refund","supplier_refund"]},"invoiceNumber":{"type":"string","minLength":1,"maxLength":255,"pattern":"\\S","description":"The number of the invoice, as shown on the document. No control characters. It does not need to be unique."},"invoiceDate":{"type":"string","format":"date","description":"Date of the invoice, `YYYY-MM-DD`. A date-time is a 422. A date in a closed month or more than 24 months ahead is loaded with a warning."},"dueDate":{"type":["string","null"],"format":"date","description":"Due date, `YYYY-MM-DD`."},"partnerExternalId":{"type":["string","null"],"minLength":1,"maxLength":255,"description":"`externalId` of the customer or supplier, a partner loaded through `partners/batch` for this same company. A partner loaded any other way, or for another company, is a 422 `unknown_reference`. Omitted or `null`: no partner. The invoice keeps its own currency; it takes nothing from the partner."},"reference":{"type":["string","null"],"minLength":1,"pattern":"\\S","description":"Free reference of the invoice (an order number, for example)."},"currency":{"type":"string","pattern":"^[A-Z]{3}$","description":"Currency of the invoice, three uppercase letters (ISO 4217, `EUR`). Amounts are in this currency."},"untaxedAmount":{"type":"string","pattern":"^-?\\d{1,13}(\\.\\d{1,2})?$","description":"Amount before taxes, as a decimal string: up to 13 integer digits and up to 2 decimals, with a dot (`\"1500.25\"`). A JSON number is a 422."},"taxAmount":{"type":"string","pattern":"^-?\\d{1,13}(\\.\\d{1,2})?$","description":"Tax amount, with the format of `untaxedAmount`."},"totalAmount":{"type":"string","pattern":"^-?\\d{1,13}(\\.\\d{1,2})?$","description":"Total amount, with the format of `untaxedAmount`."},"withholdingTaxAmount":{"type":["string","null"],"pattern":"^-?\\d{1,13}(\\.\\d{1,2})?$","description":"Withholding tax amount, with the format of `untaxedAmount`."},"status":{"type":"string","description":"Status of the document. `paid` makes the invoice paid; any other status makes it unpaid. `draft` and `cancelled` invoices do not count in the receivables and payables reports. Any other value is a 422.","enum":["draft","posted","paid","cancelled"]},"journal":{"type":["string","null"],"minLength":1,"maxLength":255,"description":"Code of the journal of the invoice. The first time a code is used in the company, a journal with that code is created; it can be renamed in Fibady and the name is kept. The same code used by a journal entry is the same journal. Omitted or `null`: no journal. Send it every time."},"lines":{"type":"array","minItems":1,"maxItems":200,"description":"The lines of the invoice, in order. They replace the stored ones by position.","items":{"type":"object","additionalProperties":false,"properties":{"description":{"type":["string","null"],"minLength":1,"pattern":"\\S","description":"Description of the line."},"quantity":{"type":["string","null"],"pattern":"^-?\\d{1,9}(\\.\\d{1,6})?$","description":"Quantity, as a decimal string: up to 9 integer digits and up to 6 decimals. Omitted or `null` is `\"1\"`."},"unitPrice":{"type":"string","pattern":"^-?\\d{1,11}(\\.\\d{1,4})?$","description":"Unit price, as a decimal string: up to 11 integer digits and up to 4 decimals."},"untaxedAmount":{"type":"string","pattern":"^-?\\d{1,13}(\\.\\d{1,2})?$","description":"Amount of the line before taxes, with the format of the header amounts."},"taxAmount":{"type":"string","pattern":"^-?\\d{1,13}(\\.\\d{1,2})?$","description":"Tax amount of the line."},"totalAmount":{"type":"string","pattern":"^-?\\d{1,13}(\\.\\d{1,2})?$","description":"Total amount of the line."},"taxRate":{"type":["string","null"],"pattern":"^\\d{1,2}(\\.\\d{1,4})?$","description":"Tax rate as a fraction, not a percentage: `\"0.21\"` is 21 %. Up to 4 decimals."},"accountCode":{"type":["string","null"],"minLength":1,"maxLength":255,"description":"Code of an account of the company's chart of accounts, loaded in any way. An unknown code is a 422 `unknown_reference`; accounts are never created from an invoice."}},"required":["unitPrice","untaxedAmount","taxAmount","totalAmount"]}}},"required":["externalId","invoiceType","invoiceNumber","invoiceDate","currency","untaxedAmount","taxAmount","totalAmount","status","lines"]}}},"required":["items"]},"examples":{"customer_invoice":{"summary":"An invoice issued to a customer","value":{"items":[{"externalId":"example-invoice-2026-0001","invoiceType":"customer_invoice","invoiceNumber":"2026-0001","invoiceDate":"2026-09-30","dueDate":"2026-10-30","partnerExternalId":"example-partner-0001","currency":"EUR","untaxedAmount":"1000.00","taxAmount":"210.00","totalAmount":"1210.00","status":"posted","journal":"SALES","lines":[{"description":"Product A, September deliveries","quantity":"10","unitPrice":"100.00","untaxedAmount":"1000.00","taxAmount":"210.00","totalAmount":"1210.00","taxRate":"0.21","accountCode":"7000001"}]}]}}}}}},"x-codeSamples":[{"lang":"Shell","label":"curl","source":": \"${FIBADY_ACCESS_TOKEN:?set it to the access_token of POST /v1/oauth/token, or to the output of fibady_token after the Quickstart}\" &&\nprintf 'header = \"Authorization: Bearer %s\"\\n' \"$FIBADY_ACCESS_TOKEN\" \\\n  | curl -sS -K - 'https://public-api.fibady.com/v1/companies/01K8EXAMP0C0MPANY000000001/invoices/batch' \\\n    -H 'Content-Type: application/json' \\\n    -H 'Idempotency-Key: example-invoices-batch-0001' \\\n    --data-binary '{\n      \"items\": [\n        {\n          \"externalId\": \"example-invoice-2026-0001\",\n          \"invoiceType\": \"customer_invoice\",\n          \"invoiceNumber\": \"2026-0001\",\n          \"invoiceDate\": \"2026-09-30\",\n          \"dueDate\": \"2026-10-30\",\n          \"partnerExternalId\": \"example-partner-0001\",\n          \"currency\": \"EUR\",\n          \"untaxedAmount\": \"1000.00\",\n          \"taxAmount\": \"210.00\",\n          \"totalAmount\": \"1210.00\",\n          \"status\": \"posted\",\n          \"journal\": \"SALES\",\n          \"lines\": [\n            {\n              \"description\": \"Product A, September deliveries\",\n              \"quantity\": \"10\",\n              \"unitPrice\": \"100.00\",\n              \"untaxedAmount\": \"1000.00\",\n              \"taxAmount\": \"210.00\",\n              \"totalAmount\": \"1210.00\",\n              \"taxRate\": \"0.21\",\n              \"accountCode\": \"7000001\"\n            }\n          ]\n        }\n      ]\n    }'"}]}},"/v1/companies/{companyId}/invoices/batch-delete":{"post":{"operationId":"deleteInvoices","summary":"Delete invoices","description":"Deletes up to 500 invoices of the company, by the `externalId` they were written with through `invoices/batch`. An invoice is deleted with its lines, their analytic allocations and its payments. Only invoices written through the API into the company of the path are deleted; an invoice from any other source is never touched, even with the same `invoiceNumber`.\n\nEach requested `externalId` comes back `deleted`, or `not_found` when the company has no live invoice with that `externalId` written through the API (it never existed, it was already deleted, or it belongs to another company): nothing tells those cases apart. An invoice that was accepted but never written (`unprojectable`) is deleted too, with `internalId: null`. After a deletion, `GET` of that `externalId` is a 404, and repeating the deletion returns `not_found`.\n\nConfirmation: when the company has at least 10 live invoices written through the API and the batch would delete more than 30% of them, send `expectedDeletes` with the number of items of the batch; without it the response is 409 `delete_confirmation_required`, with `details.candidateCount` (invoices that would be deleted) and `details.universeSize` (live invoices), and nothing is deleted. When sent, `expectedDeletes` must be the number of items of the batch, even if no confirmation is needed; otherwise the response is 422 (`expected_deletes_mismatch`). Use `dryRun=true` to see the outcome first: it answers the same 409.\n\nDeleting a record dated in a month up to `lastClosedMonth` is not blocked: it carries the warning `date_in_closed_period` and recalculates the budget variance and the forecast of that month. The date is the stored `invoiceDate` of the invoice.\n\nAllowed only when the company's invoices are fed by the API; otherwise 409 `source_not_owned`.\n\nValidation is atomic: if any item is invalid, the response is 422 and nothing is deleted. The deletion itself is not atomic: after a 5xx or a timeout, send the same deletion again BEFORE sending any of those invoices through `invoices/batch`; a repeated deletion completes it. `reportsVisibility` may stay `pending` after an invoice deletion, until the reports of the whole company are rebuilt, which in a large company may not happen soon. It is not a deletion failure: do not wait for `visible` to consider the invoices deleted.\n\nSending a deleted `externalId` again through `invoices/batch` creates the invoice again (`created`), with the same `internalId` while the deleted one has not been purged. Its payments do not come back with it: send them again through `invoice-payments/batch`, after the invoice, and they come back `created` with the same `internalId`.\n\nSend an `Idempotency-Key` to make a retry return the stored response instead of deleting again; a key already used with another body, or with `invoices/batch`, is a 422 (`idempotency_key_reused`). With `dryRun=true` the batch is only checked, with the same warnings, and nothing is deleted.\n\nEvery response carries `X-Request-Id`, `Cache-Control: no-store` and `Pragma: no-cache`. Error messages follow `Accept-Language` (English by default).","tags":["Invoices"],"responses":{"200":{"description":"The invoices were deleted or, with `dryRun=true`, checked.","headers":{"Idempotent-Replayed":{"description":"Present, with `true`, only when the response is the replay of an earlier deletion with the same `Idempotency-Key` and the same body. The body of a replay is the stored one, except `reportsVisibility` and `postProcessing`, which are computed again from the run.","schema":{"type":"string"}},"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/PublicApiDeleteBatchResult"},{"$ref":"#/components/schemas/PublicApiDeleteDryRunResult"}]}}}},"400":{"description":"`api_invalid_query` (a query parameter other than `dryRun`, a repeated one, or a `dryRun` that is not `true` or `false`) or `api_invalid_json` (the body is not valid JSON).","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseWithCode"}}}},"401":{"description":"`api_token_missing` (no `Authorization: Bearer` header) or `api_token_invalid` (unknown, expired or revoked access credential, or an inactive client). Carries `WWW-Authenticate`. Request a new token.","headers":{"WWW-Authenticate":{"schema":{"type":"string"}},"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseWithCode"}}}},"402":{"description":"`auth_invalid_or_expired_license`: the account's license is not valid.","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseWithCode"}}}},"403":{"description":"`api_origin_forbidden` (the request did not come through the public API endpoint), `api_insufficient_scope` (the client lacks `invoicing:delete`, which `invoicing:write` does not include; carries `WWW-Authenticate`), `api_company_not_allowed` (the client cannot access the company, whether it exists or not) or `api_company_param_conflict` (a `companyId` in the query or in the body that does not match the path: the company is always the one of the path).","headers":{"WWW-Authenticate":{"description":"Only with `api_insufficient_scope`.","schema":{"type":"string"}},"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseWithCode"}}}},"409":{"description":"Nothing was deleted. `source_not_owned`: the company's invoices are not fed by the API; `details.reason` is `source_not_assigned` (the company has no invoice source) or `source_not_api` (it has one, and none is the API). `delete_confirmation_required`: the batch would delete a large share of the company's invoices and `expectedDeletes` was not sent; `details.candidateCount` is the number of invoices that would be deleted and `details.universeSize` the number of live invoices; send the batch again with `expectedDeletes`. `batch_in_progress`: another batch of the company is running; no `details`. `idempotency_key_in_progress`: a request with the same `Idempotency-Key` is still running; no `details`. The last two carry `Retry-After`.","headers":{"Retry-After":{"description":"Only with `batch_in_progress` and `idempotency_key_in_progress`.","schema":{"type":"integer","minimum":1}},"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicApiErrorWithDetails"},"examples":{"batch_in_progress":{"$ref":"#/components/examples/PublicApiBatchInProgress"}}}}},"413":{"description":"`api_payload_too_large` (the body is larger than 1 MiB) or `api_batch_too_large` (more than 500 items).","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseWithCode"}}}},"422":{"description":"`validation_failed`: some item is invalid, `details` says where, and nothing was deleted. `idempotency_key_reused`: the `Idempotency-Key` was already used with a different body or operation; no `details`.","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicApiValidationError"},"examples":{"validation_failed":{"$ref":"#/components/examples/PublicApiValidationFailed"}}}}},"429":{"description":"`api_rate_limited` (too many requests for the account or the client), `api_quota_exceeded` (the account's daily write quota is used up) or `api_concurrent_requests_exceeded` (too many writes of the account in flight). Retry after `Retry-After` seconds.","headers":{"Retry-After":{"$ref":"#/components/headers/RetryAfter"},"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseWithCode"},"examples":{"api_rate_limited":{"$ref":"#/components/examples/PublicApiRateLimited"}}}}},"500":{"description":"`internal_server_error`: unexpected failure. The deletion may be partially applied; send the same deletion again.","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseWithCode"}}}},"503":{"description":"`public_api_disabled` (the public API is not enabled for the client's account, during the invitation-only preview, or has been switched off; nothing was deleted) or `api_temporarily_unavailable` (a dependency failed, or there was not enough time left to start the deletion). After `api_temporarily_unavailable` the deletion may be partially applied: send the same deletion again after `Retry-After` seconds. Do not retry `public_api_disabled` at that pace: back off exponentially, with at most 5 minutes between attempts, or stop and contact Fibady quoting `X-Request-Id`.","headers":{"Retry-After":{"$ref":"#/components/headers/RetryAfter"},"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseWithCode"}}}}},"security":[{"oauth2":["invoicing:delete"]}],"parameters":[{"name":"companyId","in":"path","required":true,"description":"Company whose invoices are deleted. It must be one the client can access.","schema":{"type":"string"}},{"name":"dryRun","in":"query","required":false,"description":"`true` checks the batch and deletes nothing: no run, no quota. Only `true` or `false`; any other value, or any other query parameter, is a 400.","schema":{"type":"boolean"}},{"name":"Idempotency-Key","in":"header","required":false,"description":"Makes the deletion idempotent: the same key with the same body returns the stored response, with `Idempotent-Replayed: true`. 1 to 255 printable ASCII characters, no spaces. Ignored with `dryRun=true`.","schema":{"type":"string","pattern":"^[\\x21-\\x7E]{1,255}$"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"properties":{"items":{"type":"array","minItems":1,"maxItems":500,"description":"The invoices to delete. An `externalId` may appear only once in the batch.","items":{"type":"object","additionalProperties":false,"properties":{"externalId":{"type":"string","minLength":1,"maxLength":255,"description":"Your identifier of the invoice, as sent to `invoices/batch`. No control characters and no leading or trailing whitespace."}},"required":["externalId"]}},"expectedDeletes":{"type":["integer","null"],"description":"The number of items of the batch, to confirm a large deletion. Omitted or `null` means no confirmation. When sent, it must be exactly the number of items."}},"required":["items"]},"examples":{"one_invoice":{"summary":"One invoice, confirming how many are deleted","value":{"items":[{"externalId":"example-invoice-2026-0001"}],"expectedDeletes":1}}}}}},"x-codeSamples":[{"lang":"Shell","label":"curl","source":": \"${FIBADY_ACCESS_TOKEN:?set it to the access_token of POST /v1/oauth/token, or to the output of fibady_token after the Quickstart}\" &&\nprintf 'header = \"Authorization: Bearer %s\"\\n' \"$FIBADY_ACCESS_TOKEN\" \\\n  | curl -sS -K - 'https://public-api.fibady.com/v1/companies/01K8EXAMP0C0MPANY000000001/invoices/batch-delete' \\\n    -H 'Content-Type: application/json' \\\n    -H 'Idempotency-Key: example-invoices-batch-delete-0001' \\\n    --data-binary '{\n      \"items\": [\n        {\n          \"externalId\": \"example-invoice-2026-0001\"\n        }\n      ],\n      \"expectedDeletes\": 1\n    }'"}]}},"/v1/companies/{companyId}/journal-entries":{"get":{"operationId":"listJournalEntries","summary":"List journal entries","description":"The journal entries written to this company with `POST /v1/companies/{companyId}/journal-entries/batch`, one summary per record: its `externalId`, `projected` (with its `internalId`) or `pending`, and `updatedAt`. It returns nothing else of the record. Use it to reconcile what Fibady holds with your own system. Any client of the account with access to the company can list them, not only the one that wrote them.\n\nOnly records written through the API are listed: a journal entry that another source loaded (manual entry, a CSV import, an ERP connector) is not. Deleted records are not listed either.\n\nRecords come in an internal order, not by `externalId`. Paginate with `cursor` until `nextCursor` is `null`; a page may be shorter than `limit`, even empty, with a non-null `nextCursor`.\n\nFor an incremental sync, pass `updatedSince` to get only the records changed since then. Every page of a pass returns the same `asOf`: start the next pass with `updatedSince` set to it, and no change in between is missed (a record may appear in two passes). `updatedSince` needs a time zone offset: send `Z`, or percent-encode the `+` of a positive offset as `%2B` (an unencoded `+` reads as a space and gives 400).\n\nEvery response carries `X-Request-Id`, `Cache-Control: no-store` and `Pragma: no-cache`. Only the 429 and the 503 carry `Retry-After`. Error messages follow `Accept-Language` (English by default).","tags":["Journal entries"],"responses":{"200":{"description":"One page of journal entries.","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicApiRecordPage"}}}},"400":{"description":"`api_invalid_query`: an unknown or repeated query parameter, a `limit` that is not an integer from 1 to 500, an `updatedSince` that is not an RFC 3339 date-time with a time zone offset, or a `cursor` that is malformed, comes from another list or company, or is sent with another `updatedSince`.","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseWithCode"}}}},"401":{"description":"`api_token_missing` (no `Authorization: Bearer` header) or `api_token_invalid` (unknown, expired or revoked access credential, or an inactive client). Carries `WWW-Authenticate`. Request a new token.","headers":{"WWW-Authenticate":{"schema":{"type":"string"}},"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseWithCode"}}}},"402":{"description":"`auth_invalid_or_expired_license`: the account's license is not valid.","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseWithCode"}}}},"403":{"description":"`api_origin_forbidden` (the request did not come through the public API endpoint), `api_insufficient_scope` (the client lacks `accounting:read`; carries `WWW-Authenticate`), `api_company_not_allowed` (the client cannot access the company, whether it exists or not) or `api_company_param_conflict` (a `companyId` query parameter that does not match the path).","headers":{"WWW-Authenticate":{"description":"Only with `api_insufficient_scope`.","schema":{"type":"string"}},"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseWithCode"}}}},"429":{"description":"`api_rate_limited`: too many requests for the account or the client. Retry after `Retry-After` seconds.","headers":{"Retry-After":{"$ref":"#/components/headers/RetryAfter"},"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseWithCode"},"examples":{"api_rate_limited":{"$ref":"#/components/examples/PublicApiRateLimited"}}}}},"500":{"description":"`internal_server_error`: unexpected failure. Safe to retry with backoff.","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseWithCode"}}}},"503":{"description":"`public_api_disabled` (the public API is not enabled for the client's account, during the invitation-only preview, or has been switched off) or `api_temporarily_unavailable` (a dependency failed). Retry `api_temporarily_unavailable` after `Retry-After` seconds. Do not retry `public_api_disabled` at that pace: back off exponentially, with at most 5 minutes between attempts, or stop and contact Fibady quoting `X-Request-Id`.","headers":{"Retry-After":{"$ref":"#/components/headers/RetryAfter"},"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseWithCode"}}}}},"security":[{"oauth2":["accounting:read"]}],"parameters":[{"name":"companyId","in":"path","required":true,"description":"Company whose journal entries are listed. It must be one the client can access.","schema":{"type":"string"}},{"name":"limit","in":"query","required":false,"description":"Page size, 1 to 500. Defaults to 500.","schema":{"type":"integer","minimum":1,"maximum":500,"default":500}},{"name":"cursor","in":"query","required":false,"description":"Opaque `nextCursor` from the previous page of the same list and company. With it, `updatedSince` may be omitted; if sent, it must be the value the pass started with.","schema":{"type":"string","maxLength":2048}},{"name":"updatedSince","in":"query","required":false,"description":"Only records changed at or after this instant: an RFC 3339 date-time with a time zone offset (`Z` or `±HH:MM`) and up to 6 fractional digits, such as `2026-10-10T08:15:30.123456Z`. Percent-encode `+` as `%2B`. A future instant gives an empty last page.","schema":{"type":"string","format":"date-time"}}],"x-codeSamples":[{"lang":"Shell","label":"curl","source":": \"${FIBADY_ACCESS_TOKEN:?set it to the access_token of POST /v1/oauth/token, or to the output of fibady_token after the Quickstart}\" &&\nprintf 'header = \"Authorization: Bearer %s\"\\n' \"$FIBADY_ACCESS_TOKEN\" \\\n  | curl -sS -K - 'https://public-api.fibady.com/v1/companies/01K8EXAMP0C0MPANY000000001/journal-entries'"}]}},"/v1/companies/{companyId}/journal-entries/{externalId}":{"get":{"operationId":"getJournalEntry","summary":"Get journal entry state","description":"Whether a journal entry written to this company with `POST /v1/companies/{companyId}/journal-entries/batch` is in Fibady, looked up by the `externalId` it was written with: `projected`, with its `internalId`, or `pending`. It returns nothing else of the record. Use it to confirm a record after a write whose result you could not read, before sending it again. Any client of the account with access to the company can read it, not only the one that wrote it.\n\nOnly records written through the API are found: a journal entry that another source loaded (manual entry, a CSV import, an ERP connector) gives 404 even if it carries the same identifier. An `externalId` that does not exist, belongs to another company, belongs to a record of another kind (an invoice's `externalId` asked for as a journal entry) or is malformed gives the same 404.\n\nPercent-encode the `externalId` in the path.\n\nEvery response carries `X-Request-Id`, `Cache-Control: no-store` and `Pragma: no-cache`. Only the 429 and the 503 carry `Retry-After`. Error messages follow `Accept-Language` (English by default).","tags":["Journal entries"],"responses":{"200":{"description":"The state of the record.","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicApiRecordStatus"}}}},"400":{"description":"`api_invalid_query`: the operation takes no query parameters, and one was sent.","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseWithCode"}}}},"401":{"description":"`api_token_missing` (no `Authorization: Bearer` header) or `api_token_invalid` (unknown, expired or revoked access credential, or an inactive client). Carries `WWW-Authenticate`. Request a new token.","headers":{"WWW-Authenticate":{"schema":{"type":"string"}},"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseWithCode"}}}},"402":{"description":"`auth_invalid_or_expired_license`: the account's license is not valid.","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseWithCode"}}}},"403":{"description":"`api_origin_forbidden` (the request did not come through the public API endpoint), `api_insufficient_scope` (the client lacks `accounting:read`; carries `WWW-Authenticate`), `api_company_not_allowed` (the client cannot access the company, whether it exists or not) or `api_company_param_conflict` (a `companyId` query parameter that does not match the path).","headers":{"WWW-Authenticate":{"description":"Only with `api_insufficient_scope`.","schema":{"type":"string"}},"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseWithCode"}}}},"404":{"description":"`api_resource_not_found`: there is no such journal entry written through the API for this company. The same response for an `externalId` that does not exist, belongs to another company, belongs to a record of another kind or from another source, or is malformed.","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseWithCode"}}}},"429":{"description":"`api_rate_limited`: too many requests for the account or the client. Retry after `Retry-After` seconds.","headers":{"Retry-After":{"$ref":"#/components/headers/RetryAfter"},"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseWithCode"},"examples":{"api_rate_limited":{"$ref":"#/components/examples/PublicApiRateLimited"}}}}},"500":{"description":"`internal_server_error`: unexpected failure. Safe to retry with backoff.","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseWithCode"}}}},"503":{"description":"`public_api_disabled` (the public API is not enabled for the client's account, during the invitation-only preview, or has been switched off) or `api_temporarily_unavailable` (a dependency failed). Retry `api_temporarily_unavailable` after `Retry-After` seconds. Do not retry `public_api_disabled` at that pace: back off exponentially, with at most 5 minutes between attempts, or stop and contact Fibady quoting `X-Request-Id`.","headers":{"Retry-After":{"$ref":"#/components/headers/RetryAfter"},"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseWithCode"}}}}},"security":[{"oauth2":["accounting:read"]}],"parameters":[{"name":"companyId","in":"path","required":true,"description":"Company the record was written to. It must be one the client can access.","schema":{"type":"string"}},{"name":"externalId","in":"path","required":true,"description":"The `externalId` the journal entry was written with, percent-encoded: 1 to 255 characters, without control characters and without leading or trailing whitespace.","schema":{"type":"string","minLength":1,"maxLength":255}}],"x-codeSamples":[{"lang":"Shell","label":"curl","source":": \"${FIBADY_ACCESS_TOKEN:?set it to the access_token of POST /v1/oauth/token, or to the output of fibady_token after the Quickstart}\" &&\nprintf 'header = \"Authorization: Bearer %s\"\\n' \"$FIBADY_ACCESS_TOKEN\" \\\n  | curl -sS -K - 'https://public-api.fibady.com/v1/companies/01K8EXAMP0C0MPANY000000001/journal-entries/example-journal-entry-0001'"}]}},"/v1/companies/{companyId}/journal-entries/batch":{"post":{"operationId":"upsertJournalEntries","summary":"Upsert journal entries","description":"Upserts up to 200 journal entries of the company, with up to 2000 lines in the whole batch (the sum of `lines` of all its entries; more is a 413). An entry is identified by its `externalId`, scoped to the company of the path. Every upsert replaces the WHOLE entry: send it complete each time.\n\nLines are identified by their POSITION in `lines`, never by any numbering of yours. Sending fewer lines than before removes the extra ones, with their analytic allocations; reordering or trimming lines changes their identity, and an allocation stays with its position. Each line goes to the debit or to the credit side, never both; the entry must balance (a difference of up to 0.01 is accepted) and cannot be all zeros.\n\n`accountCode` is the code of an account of the company, whatever its origin; an unknown code, or one that matches several stored codes only after trimming spaces, is a 422 (`unknown_reference`). Accounts are never created by this operation. `partnerExternalId` must be a partner loaded through `partners/batch` into this same company, and `analytics[].value` an analytic value loaded through `analytic-values/batch` into the dimension named in `analytics[].dimension`; anything else is a 422 (`unknown_reference`). If one of them is removed between the validation and the load, that entry comes back `unprojectable` with code `missing_parent` and the rest of the batch is loaded.\n\n`entryType`: omitted keeps the stored typology (a new entry is born ordinary); a value re-types the WHOLE entry when it differs from the stored one; `null` turns it back into an ordinary entry, the only way to undo a wrong typology. Re-sending an identical entry does not re-type it: if a user changed its typology in Fibady afterwards, that decision stands until the entry changes. When the typology cannot be applied, because a user pinned the entry as the starting opening or an omission rule claims it, the entry is loaded with the warning `entry_type_not_applied`.\n\n`journal` is the code of the journal of the entry. Its first use creates one journal with that code and name in the company, shared by journal entries and invoices; renaming it in Fibady is kept. An entry sent without `journal` is left without a journal: send it every time.\n\n`analytics` on a line replaces ALL the analytic allocations of that line, in every dimension. Omitted or `null` keeps the stored ones; `[]` removes them. Within a line and dimension the percentages add up to 100 at most and are never rescaled; on a `tag` dimension each one is 100.\n\n`currency` is optional and, when sent, must be the company's currency: amounts are always in the company's currency. An entry dated before the company's history start is a 422 (`before_history_start`). Warnings, which do not block the entry: `date_in_closed_period` (its date, or its stored date, falls in a closed month), `date_far_future` (more than 24 months ahead) and `entry_type_not_applied`.\n\nAllowed only when the company's journal entries are fed by the API; otherwise 409 `source_not_owned`.\n\nValidation is atomic: if any record is invalid, the response is 422 and nothing is written. The load itself is not atomic; after a 5xx or a timeout, send the same batch again: records already loaded come back as `unchanged`. `reportsVisibility` tells when the months the batch touched are in the reports; it may stay `pending` for a while, and that is not a failure of the load: when the batch leaves a month without entries, while the company has no reports built yet (no classified chart of accounts or no exchange rates in its scope), or until its refresh runs. Months more than 24 months ahead do not count. Do not wait for `visible` to consider a batch loaded.\n\nSend an `Idempotency-Key` to make a retry return the stored response instead of loading again. With `dryRun=true` the batch is only validated, with the same warnings, and nothing is written (no journal is created either).\n\nEvery response carries `X-Request-Id`, `Cache-Control: no-store` and `Pragma: no-cache`. Error messages follow `Accept-Language` (English by default).","tags":["Journal entries"],"responses":{"200":{"description":"The batch was loaded or, with `dryRun=true`, validated.","headers":{"Idempotent-Replayed":{"description":"Present, with `true`, only when the response is the replay of an earlier write with the same `Idempotency-Key` and the same body. The body of a replay is the stored one, except `reportsVisibility` and `postProcessing`, which are computed again from the run.","schema":{"type":"string"}},"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicApiBatchResult"}}}},"400":{"description":"`api_invalid_query` (a query parameter other than `dryRun`, a repeated one, or a `dryRun` that is not `true` or `false`) or `api_invalid_json` (the body is not valid JSON).","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseWithCode"}}}},"401":{"description":"`api_token_missing` (no `Authorization: Bearer` header) or `api_token_invalid` (unknown, expired or revoked access credential, or an inactive client). Carries `WWW-Authenticate`. Request a new token.","headers":{"WWW-Authenticate":{"schema":{"type":"string"}},"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseWithCode"}}}},"402":{"description":"`auth_invalid_or_expired_license`: the account's license is not valid.","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseWithCode"}}}},"403":{"description":"`api_origin_forbidden` (the request did not come through the public API endpoint), `api_insufficient_scope` (the client lacks `accounting:write`; carries `WWW-Authenticate`), `api_company_not_allowed` (the client cannot access the company, whether it exists or not) or `api_company_param_conflict` (a `companyId` in the query or in the body that does not match the path: the company is always the one of the path).","headers":{"WWW-Authenticate":{"description":"Only with `api_insufficient_scope`.","schema":{"type":"string"}},"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseWithCode"}}}},"409":{"description":"The batch cannot be loaded now, and nothing was written. `source_not_owned`: the company's journal entries are not fed by the API; `details.reason` is `source_not_assigned` (they have no source) or `source_not_api` (their source is not the API). `batch_in_progress`: another batch of the company is loading; no `details`. `idempotency_key_in_progress`: a write with the same `Idempotency-Key` is still running; no `details`. The last two carry `Retry-After`.","headers":{"Retry-After":{"description":"Only with `batch_in_progress` and `idempotency_key_in_progress`.","schema":{"type":"integer","minimum":1}},"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicApiErrorWithDetails"},"examples":{"batch_in_progress":{"$ref":"#/components/examples/PublicApiBatchInProgress"}}}}},"413":{"description":"`api_payload_too_large` (the body is larger than 1 MiB) or `api_batch_too_large` (more than 200 entries, or more than 2000 lines in the whole batch).","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseWithCode"}}}},"422":{"description":"`validation_failed`: some record is invalid, `details` says where, and nothing was written. `idempotency_key_reused`: the `Idempotency-Key` was already used with a different body; no `details`.","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicApiValidationError"},"examples":{"validation_failed":{"$ref":"#/components/examples/PublicApiValidationFailed"}}}}},"429":{"description":"`api_rate_limited` (too many requests for the account or the client), `api_quota_exceeded` (the account's daily write quota is used up) or `api_concurrent_requests_exceeded` (too many writes of the account in flight). Retry after `Retry-After` seconds.","headers":{"Retry-After":{"$ref":"#/components/headers/RetryAfter"},"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseWithCode"},"examples":{"api_rate_limited":{"$ref":"#/components/examples/PublicApiRateLimited"}}}}},"500":{"description":"`internal_server_error`: unexpected failure. The batch may be partially loaded; send it again.","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseWithCode"}}}},"503":{"description":"`public_api_disabled` (the public API is not enabled for the client's account, during the invitation-only preview, or has been switched off; nothing was written) or `api_temporarily_unavailable` (a dependency failed, or there was not enough time left to start the load). After `api_temporarily_unavailable` the batch may be partially loaded: send it again after `Retry-After` seconds. Do not retry `public_api_disabled` at that pace: back off exponentially, with at most 5 minutes between attempts, or stop and contact Fibady quoting `X-Request-Id`.","headers":{"Retry-After":{"$ref":"#/components/headers/RetryAfter"},"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseWithCode"}}}}},"security":[{"oauth2":["accounting:write"]}],"parameters":[{"name":"companyId","in":"path","required":true,"description":"Company whose journal entries are written. It must be one the client can access.","schema":{"type":"string"}},{"name":"dryRun","in":"query","required":false,"description":"`true` validates the batch and writes nothing: no run, no quota. Only `true` or `false`; any other value, or any other query parameter, is a 400.","schema":{"type":"boolean"}},{"name":"Idempotency-Key","in":"header","required":false,"description":"Makes the write idempotent: the same key with the same body returns the stored response, with `Idempotent-Replayed: true`. 1 to 255 printable ASCII characters, no spaces. Ignored with `dryRun=true`.","schema":{"type":"string","pattern":"^[\\x21-\\x7E]{1,255}$"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"properties":{"items":{"type":"array","minItems":1,"maxItems":200,"description":"The journal entries. An `externalId` may appear only once in the batch, and the batch carries up to 2000 lines in total.","items":{"type":"object","additionalProperties":false,"properties":{"externalId":{"type":"string","minLength":1,"maxLength":255,"description":"Your identifier of the entry: its identity within the company. No control characters and no leading or trailing whitespace."},"date":{"type":"string","format":"date","description":"Accounting date of the entry, `YYYY-MM-DD`."},"description":{"type":["string","null"],"minLength":1,"pattern":"\\S","description":"Description of the entry. It cannot be empty or blank; omit it or send `null` to leave it out."},"entryType":{"type":["string","null"],"enum":["opening","closing","regularization","other",null],"description":"Year-end typology. Omitted keeps the stored one (a new entry is ordinary); a value re-types the whole entry; `null` makes it ordinary again."},"journal":{"type":["string","null"],"minLength":1,"maxLength":255,"description":"Code of the journal. No control characters and no leading or trailing whitespace. Omitted or `null` leaves the entry without a journal."},"currency":{"type":["string","null"],"pattern":"^[A-Z]{3}$","description":"Currency of the amounts. Optional; when sent it must be the company's currency (three uppercase letters)."},"lines":{"type":"array","minItems":2,"maxItems":500,"description":"The lines of the entry, identified by their position.","items":{"type":"object","additionalProperties":false,"properties":{"accountCode":{"type":"string","minLength":1,"maxLength":255,"description":"Code of an account of the company, of any origin."},"description":{"type":["string","null"],"minLength":1,"pattern":"\\S","description":"Description of the line. It cannot be empty or blank; omitted or `null`, the line shows the description of the entry."},"debit":{"type":["string","null"],"pattern":"^\\d{1,13}(\\.\\d{1,2})?$","description":"Debit amount, as a decimal string without sign: up to 13 integer digits and up to 2 decimals, with a dot (`\"1500.25\"`). A JSON number is a 422. Omitted or `null` is `0`."},"credit":{"type":["string","null"],"pattern":"^\\d{1,13}(\\.\\d{1,2})?$","description":"Credit amount, with the same format as `debit`. Omitted or `null` is `0`."},"partnerExternalId":{"type":["string","null"],"minLength":1,"maxLength":255,"description":"`externalId` of a partner loaded through `partners/batch` into this company. Omitted or `null` leaves the line without a partner."},"dueDate":{"type":["string","null"],"format":"date","description":"Due date of the line, `YYYY-MM-DD`."},"analytics":{"type":["array","null"],"description":"Analytic allocations of the line. They replace all the stored ones of the line, in every dimension; omitted or `null` keeps them, `[]` removes them.","items":{"type":"object","additionalProperties":false,"properties":{"dimension":{"type":"string","minLength":1,"maxLength":64,"description":"Name of the dimension, as sent to `analytic-values/batch`."},"value":{"type":"string","minLength":1,"maxLength":255,"description":"`externalId` of an analytic value of that dimension."},"percentage":{"type":["string","null"],"pattern":"^\\d{1,3}(\\.\\d{1,3})?$","description":"Share of the line, greater than 0 and up to 100, with up to 3 decimals (`\"33.333\"`). Omitted or `null` is `100`. Within a line and dimension the shares add up to 100 at most; on a `tag` dimension each one is 100."}},"required":["dimension","value"]}}},"required":["accountCode"]}}},"required":["externalId","date","lines"]}}},"required":["items"]},"examples":{"rent_and_payment":{"summary":"A supplier's rent, split by region, and its payment","value":{"items":[{"externalId":"example-journal-entry-0001","date":"2026-09-30","description":"Office rent, September","journal":"PURCHASES","lines":[{"accountCode":"6210001","debit":"800.00","analytics":[{"dimension":"Region","value":"example-region-north","percentage":"60"},{"dimension":"Region","value":"example-region-south","percentage":"40"}]},{"accountCode":"4000001","credit":"800.00","partnerExternalId":"example-partner-0002","dueDate":"2026-10-10"}]},{"externalId":"example-journal-entry-0002","date":"2026-10-01","description":"Payment of the office rent, September","journal":"BANK","lines":[{"accountCode":"4000001","debit":"800.00","partnerExternalId":"example-partner-0002"},{"accountCode":"5720001","credit":"800.00"}]}]}}}}}},"x-codeSamples":[{"lang":"Shell","label":"curl","source":": \"${FIBADY_ACCESS_TOKEN:?set it to the access_token of POST /v1/oauth/token, or to the output of fibady_token after the Quickstart}\" &&\nprintf 'header = \"Authorization: Bearer %s\"\\n' \"$FIBADY_ACCESS_TOKEN\" \\\n  | curl -sS -K - 'https://public-api.fibady.com/v1/companies/01K8EXAMP0C0MPANY000000001/journal-entries/batch' \\\n    -H 'Content-Type: application/json' \\\n    -H 'Idempotency-Key: example-journal-entries-batch-0001' \\\n    --data-binary '{\n      \"items\": [\n        {\n          \"externalId\": \"example-journal-entry-0001\",\n          \"date\": \"2026-09-30\",\n          \"description\": \"Office rent, September\",\n          \"journal\": \"PURCHASES\",\n          \"lines\": [\n            {\n              \"accountCode\": \"6210001\",\n              \"debit\": \"800.00\",\n              \"analytics\": [\n                {\n                  \"dimension\": \"Region\",\n                  \"value\": \"example-region-north\",\n                  \"percentage\": \"60\"\n                },\n                {\n                  \"dimension\": \"Region\",\n                  \"value\": \"example-region-south\",\n                  \"percentage\": \"40\"\n                }\n              ]\n            },\n            {\n              \"accountCode\": \"4000001\",\n              \"credit\": \"800.00\",\n              \"partnerExternalId\": \"example-partner-0002\",\n              \"dueDate\": \"2026-10-10\"\n            }\n          ]\n        },\n        {\n          \"externalId\": \"example-journal-entry-0002\",\n          \"date\": \"2026-10-01\",\n          \"description\": \"Payment of the office rent, September\",\n          \"journal\": \"BANK\",\n          \"lines\": [\n            {\n              \"accountCode\": \"4000001\",\n              \"debit\": \"800.00\",\n              \"partnerExternalId\": \"example-partner-0002\"\n            },\n            {\n              \"accountCode\": \"5720001\",\n              \"credit\": \"800.00\"\n            }\n          ]\n        }\n      ]\n    }'"}]}},"/v1/companies/{companyId}/journal-entries/batch-delete":{"post":{"operationId":"deleteJournalEntries","summary":"Delete journal entries","description":"Deletes up to 500 journal entries of the company, by the `externalId` they were written with through `journal-entries/batch`. An entry is deleted WHOLE: its header, all its lines and their analytic allocations. Only entries written through the API into the company of the path are deleted; an entry from any other source is never touched.\n\nEach requested `externalId` comes back `deleted`, or `not_found` when the company has no live entry with that `externalId` written through the API (it never existed, it was already deleted, or it belongs to another company): nothing tells those cases apart. An entry that was accepted but never written (`unprojectable`) is deleted too, with `internalId: null`. After a deletion, `GET` of that `externalId` is a 404, and repeating the deletion returns `not_found`.\n\nConfirmation: when the company has at least 10 live journal entries written through the API and the batch would delete more than 30% of them, send `expectedDeletes` with the number of items of the batch; without it the response is 409 `delete_confirmation_required`, with `details.candidateCount` (entries that would be deleted) and `details.universeSize` (live entries), and nothing is deleted. When sent, `expectedDeletes` must be the number of items of the batch, even if no confirmation is needed; otherwise the response is 422 (`expected_deletes_mismatch`). Use `dryRun=true` to see the outcome first: it answers the same 409.\n\nDeleting a record dated in a month up to `lastClosedMonth` is not blocked: it carries the warning `date_in_closed_period` and recalculates the budget variance and the forecast of that month.\n\nAllowed only when the company's journal entries are fed by the API; otherwise 409 `source_not_owned`.\n\nValidation is atomic: if any item is invalid, the response is 422 and nothing is deleted. The deletion itself is not atomic: after a 5xx or a timeout, send the same deletion again BEFORE sending any of those entries through `journal-entries/batch`; a repeated deletion completes it. `reportsVisibility` tells when the months of the deleted entries are out of the reports; it may stay `pending` for a while, and that is not a failure of the deletion. Months more than 24 months ahead do not count.\n\nSending a deleted `externalId` again through `journal-entries/batch` creates the entry again (`created`), with the same `internalId` while the deleted one has not been purged.\n\nSend an `Idempotency-Key` to make a retry return the stored response instead of deleting again; a key already used with another body, or with `journal-entries/batch`, is a 422 (`idempotency_key_reused`). With `dryRun=true` the batch is only checked, with the same warnings, and nothing is deleted.\n\nEvery response carries `X-Request-Id`, `Cache-Control: no-store` and `Pragma: no-cache`. Error messages follow `Accept-Language` (English by default).","tags":["Journal entries"],"responses":{"200":{"description":"The entries were deleted or, with `dryRun=true`, checked.","headers":{"Idempotent-Replayed":{"description":"Present, with `true`, only when the response is the replay of an earlier deletion with the same `Idempotency-Key` and the same body. The body of a replay is the stored one, except `reportsVisibility` and `postProcessing`, which are computed again from the run.","schema":{"type":"string"}},"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/PublicApiDeleteBatchResult"},{"$ref":"#/components/schemas/PublicApiDeleteDryRunResult"}]}}}},"400":{"description":"`api_invalid_query` (a query parameter other than `dryRun`, a repeated one, or a `dryRun` that is not `true` or `false`) or `api_invalid_json` (the body is not valid JSON).","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseWithCode"}}}},"401":{"description":"`api_token_missing` (no `Authorization: Bearer` header) or `api_token_invalid` (unknown, expired or revoked access credential, or an inactive client). Carries `WWW-Authenticate`. Request a new token.","headers":{"WWW-Authenticate":{"schema":{"type":"string"}},"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseWithCode"}}}},"402":{"description":"`auth_invalid_or_expired_license`: the account's license is not valid.","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseWithCode"}}}},"403":{"description":"`api_origin_forbidden` (the request did not come through the public API endpoint), `api_insufficient_scope` (the client lacks `accounting:delete`, which `accounting:write` does not include; carries `WWW-Authenticate`), `api_company_not_allowed` (the client cannot access the company, whether it exists or not) or `api_company_param_conflict` (a `companyId` in the query or in the body that does not match the path: the company is always the one of the path).","headers":{"WWW-Authenticate":{"description":"Only with `api_insufficient_scope`.","schema":{"type":"string"}},"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseWithCode"}}}},"409":{"description":"Nothing was deleted. `source_not_owned`: the company's journal entries are not fed by the API; `details.reason` is `source_not_assigned` (they have no source) or `source_not_api` (their source is not the API). `delete_confirmation_required`: the batch would delete a large share of the company's journal entries and `expectedDeletes` was not sent; `details.candidateCount` is the number of entries that would be deleted and `details.universeSize` the number of live entries; send the batch again with `expectedDeletes`. `batch_in_progress`: another batch of the company is running; no `details`. `idempotency_key_in_progress`: a request with the same `Idempotency-Key` is still running; no `details`. The last two carry `Retry-After`.","headers":{"Retry-After":{"description":"Only with `batch_in_progress` and `idempotency_key_in_progress`.","schema":{"type":"integer","minimum":1}},"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicApiErrorWithDetails"},"examples":{"batch_in_progress":{"$ref":"#/components/examples/PublicApiBatchInProgress"}}}}},"413":{"description":"`api_payload_too_large` (the body is larger than 1 MiB) or `api_batch_too_large` (more than 500 items).","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseWithCode"}}}},"422":{"description":"`validation_failed`: some item is invalid, `details` says where, and nothing was deleted. `idempotency_key_reused`: the `Idempotency-Key` was already used with a different body or operation; no `details`.","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicApiValidationError"},"examples":{"validation_failed":{"$ref":"#/components/examples/PublicApiValidationFailed"}}}}},"429":{"description":"`api_rate_limited` (too many requests for the account or the client), `api_quota_exceeded` (the account's daily write quota is used up) or `api_concurrent_requests_exceeded` (too many writes of the account in flight). Retry after `Retry-After` seconds.","headers":{"Retry-After":{"$ref":"#/components/headers/RetryAfter"},"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseWithCode"},"examples":{"api_rate_limited":{"$ref":"#/components/examples/PublicApiRateLimited"}}}}},"500":{"description":"`internal_server_error`: unexpected failure. The deletion may be partially applied; send the same deletion again.","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseWithCode"}}}},"503":{"description":"`public_api_disabled` (the public API is not enabled for the client's account, during the invitation-only preview, or has been switched off; nothing was deleted) or `api_temporarily_unavailable` (a dependency failed, or there was not enough time left to start the deletion). After `api_temporarily_unavailable` the deletion may be partially applied: send the same deletion again after `Retry-After` seconds. Do not retry `public_api_disabled` at that pace: back off exponentially, with at most 5 minutes between attempts, or stop and contact Fibady quoting `X-Request-Id`.","headers":{"Retry-After":{"$ref":"#/components/headers/RetryAfter"},"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseWithCode"}}}}},"security":[{"oauth2":["accounting:delete"]}],"parameters":[{"name":"companyId","in":"path","required":true,"description":"Company whose journal entries are deleted. It must be one the client can access.","schema":{"type":"string"}},{"name":"dryRun","in":"query","required":false,"description":"`true` checks the batch and deletes nothing: no run, no quota. Only `true` or `false`; any other value, or any other query parameter, is a 400.","schema":{"type":"boolean"}},{"name":"Idempotency-Key","in":"header","required":false,"description":"Makes the deletion idempotent: the same key with the same body returns the stored response, with `Idempotent-Replayed: true`. 1 to 255 printable ASCII characters, no spaces. Ignored with `dryRun=true`.","schema":{"type":"string","pattern":"^[\\x21-\\x7E]{1,255}$"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"properties":{"items":{"type":"array","minItems":1,"maxItems":500,"description":"The journal entries to delete. An `externalId` may appear only once in the batch.","items":{"type":"object","additionalProperties":false,"properties":{"externalId":{"type":"string","minLength":1,"maxLength":255,"description":"Your identifier of the entry, as sent to `journal-entries/batch`. No control characters and no leading or trailing whitespace."}},"required":["externalId"]}},"expectedDeletes":{"type":["integer","null"],"description":"The number of items of the batch, to confirm a large deletion. Omitted or `null` means no confirmation. When sent, it must be exactly the number of items."}},"required":["items"]},"examples":{"one_entry":{"summary":"One journal entry, confirming how many are deleted","value":{"items":[{"externalId":"example-journal-entry-0002"}],"expectedDeletes":1}}}}}},"x-codeSamples":[{"lang":"Shell","label":"curl","source":": \"${FIBADY_ACCESS_TOKEN:?set it to the access_token of POST /v1/oauth/token, or to the output of fibady_token after the Quickstart}\" &&\nprintf 'header = \"Authorization: Bearer %s\"\\n' \"$FIBADY_ACCESS_TOKEN\" \\\n  | curl -sS -K - 'https://public-api.fibady.com/v1/companies/01K8EXAMP0C0MPANY000000001/journal-entries/batch-delete' \\\n    -H 'Content-Type: application/json' \\\n    -H 'Idempotency-Key: example-journal-entries-batch-delete-0001' \\\n    --data-binary '{\n      \"items\": [\n        {\n          \"externalId\": \"example-journal-entry-0002\"\n        }\n      ],\n      \"expectedDeletes\": 1\n    }'"}]}},"/v1/companies/{companyId}/partners":{"get":{"operationId":"listPartners","summary":"List partners","description":"The partners (customers and suppliers) written to this company with `POST /v1/companies/{companyId}/partners/batch`, one summary per record: its `externalId`, `projected` (with its `internalId`) or `pending`, and `updatedAt`. It returns nothing else of the record. Use it to reconcile what Fibady holds with your own system. Any client of the account with access to the company can list them, not only the one that wrote them.\n\nOnly records written through the API are listed: a partner that another source loaded (manual entry, a CSV import, an ERP connector) is not. Deleted records are not listed either.\n\nRecords come in an internal order, not by `externalId`. Paginate with `cursor` until `nextCursor` is `null`; a page may be shorter than `limit`, even empty, with a non-null `nextCursor`.\n\nFor an incremental sync, pass `updatedSince` to get only the records changed since then. Every page of a pass returns the same `asOf`: start the next pass with `updatedSince` set to it, and no change in between is missed (a record may appear in two passes). `updatedSince` needs a time zone offset: send `Z`, or percent-encode the `+` of a positive offset as `%2B` (an unencoded `+` reads as a space and gives 400).\n\nEvery response carries `X-Request-Id`, `Cache-Control: no-store` and `Pragma: no-cache`. Only the 429 and the 503 carry `Retry-After`. Error messages follow `Accept-Language` (English by default).","tags":["Partners"],"responses":{"200":{"description":"One page of partners.","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicApiRecordPage"}}}},"400":{"description":"`api_invalid_query`: an unknown or repeated query parameter, a `limit` that is not an integer from 1 to 500, an `updatedSince` that is not an RFC 3339 date-time with a time zone offset, or a `cursor` that is malformed, comes from another list or company, or is sent with another `updatedSince`.","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseWithCode"}}}},"401":{"description":"`api_token_missing` (no `Authorization: Bearer` header) or `api_token_invalid` (unknown, expired or revoked access credential, or an inactive client). Carries `WWW-Authenticate`. Request a new token.","headers":{"WWW-Authenticate":{"schema":{"type":"string"}},"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseWithCode"}}}},"402":{"description":"`auth_invalid_or_expired_license`: the account's license is not valid.","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseWithCode"}}}},"403":{"description":"`api_origin_forbidden` (the request did not come through the public API endpoint), `api_insufficient_scope` (the client lacks `invoicing:read`; carries `WWW-Authenticate`), `api_company_not_allowed` (the client cannot access the company, whether it exists or not) or `api_company_param_conflict` (a `companyId` query parameter that does not match the path).","headers":{"WWW-Authenticate":{"description":"Only with `api_insufficient_scope`.","schema":{"type":"string"}},"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseWithCode"}}}},"429":{"description":"`api_rate_limited`: too many requests for the account or the client. Retry after `Retry-After` seconds.","headers":{"Retry-After":{"$ref":"#/components/headers/RetryAfter"},"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseWithCode"},"examples":{"api_rate_limited":{"$ref":"#/components/examples/PublicApiRateLimited"}}}}},"500":{"description":"`internal_server_error`: unexpected failure. Safe to retry with backoff.","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseWithCode"}}}},"503":{"description":"`public_api_disabled` (the public API is not enabled for the client's account, during the invitation-only preview, or has been switched off) or `api_temporarily_unavailable` (a dependency failed). Retry `api_temporarily_unavailable` after `Retry-After` seconds. Do not retry `public_api_disabled` at that pace: back off exponentially, with at most 5 minutes between attempts, or stop and contact Fibady quoting `X-Request-Id`.","headers":{"Retry-After":{"$ref":"#/components/headers/RetryAfter"},"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseWithCode"}}}}},"security":[{"oauth2":["invoicing:read"]}],"parameters":[{"name":"companyId","in":"path","required":true,"description":"Company whose partners are listed. It must be one the client can access.","schema":{"type":"string"}},{"name":"limit","in":"query","required":false,"description":"Page size, 1 to 500. Defaults to 500.","schema":{"type":"integer","minimum":1,"maximum":500,"default":500}},{"name":"cursor","in":"query","required":false,"description":"Opaque `nextCursor` from the previous page of the same list and company. With it, `updatedSince` may be omitted; if sent, it must be the value the pass started with.","schema":{"type":"string","maxLength":2048}},{"name":"updatedSince","in":"query","required":false,"description":"Only records changed at or after this instant: an RFC 3339 date-time with a time zone offset (`Z` or `±HH:MM`) and up to 6 fractional digits, such as `2026-10-10T08:15:30.123456Z`. Percent-encode `+` as `%2B`. A future instant gives an empty last page.","schema":{"type":"string","format":"date-time"}}],"x-codeSamples":[{"lang":"Shell","label":"curl","source":": \"${FIBADY_ACCESS_TOKEN:?set it to the access_token of POST /v1/oauth/token, or to the output of fibady_token after the Quickstart}\" &&\nprintf 'header = \"Authorization: Bearer %s\"\\n' \"$FIBADY_ACCESS_TOKEN\" \\\n  | curl -sS -K - 'https://public-api.fibady.com/v1/companies/01K8EXAMP0C0MPANY000000001/partners'"}]}},"/v1/companies/{companyId}/partners/{externalId}":{"get":{"operationId":"getPartner","summary":"Get partner state","description":"Whether a partner (a customer or a supplier) written to this company with `POST /v1/companies/{companyId}/partners/batch` is in Fibady, looked up by the `externalId` it was written with: `projected`, with its `internalId`, or `pending`. It returns nothing else of the record. Use it to confirm a record after a write whose result you could not read, before sending it again. Any client of the account with access to the company can read it, not only the one that wrote it.\n\nOnly records written through the API are found: a partner that another source loaded (manual entry, a CSV import, an ERP connector) gives 404 even if it carries the same identifier. An `externalId` that does not exist, belongs to another company or is malformed gives the same 404. The same `externalId` in two companies are two different partners.\n\nPercent-encode the `externalId` in the path.\n\nEvery response carries `X-Request-Id`, `Cache-Control: no-store` and `Pragma: no-cache`. Only the 429 and the 503 carry `Retry-After`. Error messages follow `Accept-Language` (English by default).","tags":["Partners"],"responses":{"200":{"description":"The state of the record.","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicApiRecordStatus"}}}},"400":{"description":"`api_invalid_query`: the operation takes no query parameters, and one was sent.","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseWithCode"}}}},"401":{"description":"`api_token_missing` (no `Authorization: Bearer` header) or `api_token_invalid` (unknown, expired or revoked access credential, or an inactive client). Carries `WWW-Authenticate`. Request a new token.","headers":{"WWW-Authenticate":{"schema":{"type":"string"}},"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseWithCode"}}}},"402":{"description":"`auth_invalid_or_expired_license`: the account's license is not valid.","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseWithCode"}}}},"403":{"description":"`api_origin_forbidden` (the request did not come through the public API endpoint), `api_insufficient_scope` (the client lacks `invoicing:read`; carries `WWW-Authenticate`), `api_company_not_allowed` (the client cannot access the company, whether it exists or not) or `api_company_param_conflict` (a `companyId` query parameter that does not match the path).","headers":{"WWW-Authenticate":{"description":"Only with `api_insufficient_scope`.","schema":{"type":"string"}},"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseWithCode"}}}},"404":{"description":"`api_resource_not_found`: there is no such partner written through the API for this company. The same response for an `externalId` that does not exist, belongs to another company, belongs to a record from another source or is malformed.","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseWithCode"}}}},"429":{"description":"`api_rate_limited`: too many requests for the account or the client. Retry after `Retry-After` seconds.","headers":{"Retry-After":{"$ref":"#/components/headers/RetryAfter"},"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseWithCode"},"examples":{"api_rate_limited":{"$ref":"#/components/examples/PublicApiRateLimited"}}}}},"500":{"description":"`internal_server_error`: unexpected failure. Safe to retry with backoff.","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseWithCode"}}}},"503":{"description":"`public_api_disabled` (the public API is not enabled for the client's account, during the invitation-only preview, or has been switched off) or `api_temporarily_unavailable` (a dependency failed). Retry `api_temporarily_unavailable` after `Retry-After` seconds. Do not retry `public_api_disabled` at that pace: back off exponentially, with at most 5 minutes between attempts, or stop and contact Fibady quoting `X-Request-Id`.","headers":{"Retry-After":{"$ref":"#/components/headers/RetryAfter"},"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseWithCode"}}}}},"security":[{"oauth2":["invoicing:read"]}],"parameters":[{"name":"companyId","in":"path","required":true,"description":"Company the record was written to. It must be one the client can access.","schema":{"type":"string"}},{"name":"externalId","in":"path","required":true,"description":"The `externalId` the partner was written with, percent-encoded: 1 to 255 characters, without control characters and without leading or trailing whitespace.","schema":{"type":"string","minLength":1,"maxLength":255}}],"x-codeSamples":[{"lang":"Shell","label":"curl","source":": \"${FIBADY_ACCESS_TOKEN:?set it to the access_token of POST /v1/oauth/token, or to the output of fibady_token after the Quickstart}\" &&\nprintf 'header = \"Authorization: Bearer %s\"\\n' \"$FIBADY_ACCESS_TOKEN\" \\\n  | curl -sS -K - 'https://public-api.fibady.com/v1/companies/01K8EXAMP0C0MPANY000000001/partners/example-partner-0001'"}]}},"/v1/companies/{companyId}/partners/batch":{"post":{"operationId":"upsertPartners","summary":"Upsert partners","description":"Upserts up to 500 partners of the company: its customers, its suppliers, or both. A partner is identified by its `externalId`, which is scoped to the company of the path: the same `externalId` sent to two companies creates two different partners, and a batch never changes the partners of another company. A partner loaded by anything other than the API (an ERP connector, a CSV import, manual entry) is never matched or changed, even with the same tax identifier or name: sending it creates another partner.\n\nEvery upsert writes the whole partner: an optional field that is not sent, or is `null`, is cleared. An empty or blank string is not a way to clear a field: it is a 422. Partners are never deleted through this operation; send `active: false` to deactivate one. A partner that was deleted in Fibady is restored when a batch writes it again.\n\nAllowed only when the company's journal entries or invoices are fed by the API and neither comes from an ERP connector; otherwise 409.\n\nValidation is atomic: if any record is invalid, the response is 422 and nothing is written. The load itself is not atomic; after a 5xx or a timeout, send the same batch again: records already loaded come back as `unchanged`. A batch of partners changes nothing in the reports, so `reportsVisibility` is `visible`.\n\nSend an `Idempotency-Key` to make a retry return the stored response instead of loading again. With `dryRun=true` the batch is only validated.\n\nEvery response carries `X-Request-Id`, `Cache-Control: no-store` and `Pragma: no-cache`. Error messages follow `Accept-Language` (English by default).","tags":["Partners"],"responses":{"200":{"description":"The batch was loaded or, with `dryRun=true`, validated.","headers":{"Idempotent-Replayed":{"description":"Present, with `true`, only when the response is the replay of an earlier write with the same `Idempotency-Key` and the same body. The body of a replay is the stored one, except `reportsVisibility` and `postProcessing`, which are computed again from the run.","schema":{"type":"string"}},"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicApiBatchResult"}}}},"400":{"description":"`api_invalid_query` (a query parameter other than `dryRun`, a repeated one, or a `dryRun` that is not `true` or `false`) or `api_invalid_json` (the body is not valid JSON).","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseWithCode"}}}},"401":{"description":"`api_token_missing` (no `Authorization: Bearer` header) or `api_token_invalid` (unknown, expired or revoked access credential, or an inactive client). Carries `WWW-Authenticate`. Request a new token.","headers":{"WWW-Authenticate":{"schema":{"type":"string"}},"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseWithCode"}}}},"402":{"description":"`auth_invalid_or_expired_license`: the account's license is not valid.","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseWithCode"}}}},"403":{"description":"`api_origin_forbidden` (the request did not come through the public API endpoint), `api_insufficient_scope` (the client lacks `invoicing:write`; carries `WWW-Authenticate`), `api_company_not_allowed` (the client cannot access the company, whether it exists or not) or `api_company_param_conflict` (a `companyId` in the query or in the body that does not match the path: the company is always the one of the path).","headers":{"WWW-Authenticate":{"description":"Only with `api_insufficient_scope`.","schema":{"type":"string"}},"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseWithCode"}}}},"409":{"description":"The batch cannot be loaded now, and nothing was written. `source_not_owned`: the company's accounting is not fed by the API; `details.reason` is `source_not_assigned` (neither journal entries nor invoices have a source) or `source_not_api` (they have one, and none is the API). `erp_api_mix_not_supported`: one of the two is fed by the API and the other by an ERP connector; `details` is empty. `batch_in_progress`: another batch of the company is loading; no `details`. `idempotency_key_in_progress`: a write with the same `Idempotency-Key` is still running; no `details`. The last two carry `Retry-After`.","headers":{"Retry-After":{"description":"Only with `batch_in_progress` and `idempotency_key_in_progress`.","schema":{"type":"integer","minimum":1}},"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicApiErrorWithDetails"},"examples":{"batch_in_progress":{"$ref":"#/components/examples/PublicApiBatchInProgress"}}}}},"413":{"description":"`api_payload_too_large` (the body is larger than 1 MiB) or `api_batch_too_large` (more than 500 records).","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseWithCode"}}}},"422":{"description":"`validation_failed`: some record is invalid, `details` says where, and nothing was written. `idempotency_key_reused`: the `Idempotency-Key` was already used with a different body; no `details`.","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicApiValidationError"},"examples":{"validation_failed":{"$ref":"#/components/examples/PublicApiValidationFailed"}}}}},"429":{"description":"`api_rate_limited` (too many requests for the account or the client), `api_quota_exceeded` (the account's daily write quota is used up) or `api_concurrent_requests_exceeded` (too many writes of the account in flight). Retry after `Retry-After` seconds.","headers":{"Retry-After":{"$ref":"#/components/headers/RetryAfter"},"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseWithCode"},"examples":{"api_rate_limited":{"$ref":"#/components/examples/PublicApiRateLimited"}}}}},"500":{"description":"`internal_server_error`: unexpected failure. The batch may be partially loaded; send it again.","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseWithCode"}}}},"503":{"description":"`public_api_disabled` (the public API is not enabled for the client's account, during the invitation-only preview, or has been switched off; nothing was written) or `api_temporarily_unavailable` (a dependency failed, or there was not enough time left to start the load). After `api_temporarily_unavailable` the batch may be partially loaded: send it again after `Retry-After` seconds. Do not retry `public_api_disabled` at that pace: back off exponentially, with at most 5 minutes between attempts, or stop and contact Fibady quoting `X-Request-Id`.","headers":{"Retry-After":{"$ref":"#/components/headers/RetryAfter"},"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseWithCode"}}}}},"security":[{"oauth2":["invoicing:write"]}],"parameters":[{"name":"companyId","in":"path","required":true,"description":"Company whose partners are written. It must be one the client can access.","schema":{"type":"string"}},{"name":"dryRun","in":"query","required":false,"description":"`true` validates the batch and writes nothing: no run, no quota. Only `true` or `false`; any other value, or any other query parameter, is a 400.","schema":{"type":"boolean"}},{"name":"Idempotency-Key","in":"header","required":false,"description":"Makes the write idempotent: the same key with the same body returns the stored response, with `Idempotent-Replayed: true`. 1 to 255 printable ASCII characters, no spaces. Ignored with `dryRun=true`.","schema":{"type":"string","pattern":"^[\\x21-\\x7E]{1,255}$"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"properties":{"items":{"type":"array","minItems":1,"maxItems":500,"description":"The partners. An `externalId` may appear only once in the batch.","items":{"type":"object","additionalProperties":false,"properties":{"externalId":{"type":"string","minLength":1,"maxLength":255,"description":"Your identifier of the partner: its identity within the company. No control characters and no leading or trailing whitespace."},"type":{"type":"string","description":"Whether the partner is a customer (`client`), a supplier or both. Any other value is a 422.","enum":["client","supplier","both"]},"name":{"type":"string","minLength":1,"pattern":"\\S","description":"Partner name. Stored as sent; it cannot be empty or blank."},"taxId":{"type":["string","null"],"minLength":1,"pattern":"\\S","description":"Tax identifier (VAT number, NIF, CUIT). Stored as sent, without validating its format."},"isCompany":{"type":["boolean","null"],"default":false,"description":"`true` for a legal entity, `false` for an individual. Omitted or `null` is `false`."},"email":{"type":["string","null"],"minLength":1,"pattern":"\\S","description":"Contact email. Stored as sent, without validating its format."},"phone":{"type":["string","null"],"minLength":1,"pattern":"\\S","description":"Contact phone. Stored as sent."},"address":{"type":["string","null"],"minLength":1,"pattern":"\\S","description":"Postal address, as a single line."},"country":{"type":["string","null"],"minLength":1,"pattern":"\\S","description":"Country. Free text, stored as sent."},"city":{"type":["string","null"],"minLength":1,"pattern":"\\S","description":"City. Free text, stored as sent."},"accountNumber":{"type":["string","null"],"minLength":1,"pattern":"\\S","description":"Bank account of the partner (an IBAN, for example). Stored as sent."},"activity":{"type":["string","null"],"minLength":1,"pattern":"\\S","description":"Business activity of the partner. Free text."},"paymentTermsDays":{"type":["integer","null"],"minimum":0,"maximum":3650,"description":"Payment terms, in days. An integer; `0` is payment on receipt."},"active":{"type":["boolean","null"],"default":true,"description":"`false` deactivates the partner. Omitted or `null` is `true`."}},"required":["externalId","type","name"]}}},"required":["items"]},"examples":{"customer_and_supplier":{"summary":"A customer and a supplier","value":{"items":[{"externalId":"example-partner-0001","type":"client","name":"Example Customer","taxId":"99999999R","isCompany":false,"email":"billing@example.com","country":"ES","paymentTermsDays":30},{"externalId":"example-partner-0002","type":"supplier","name":"Example Supplies GmbH","isCompany":true,"email":"invoices@example.com","country":"DE","accountNumber":"DE89370400440532013000"}]}}}}}},"x-codeSamples":[{"lang":"Shell","label":"curl","source":": \"${FIBADY_ACCESS_TOKEN:?set it to the access_token of POST /v1/oauth/token, or to the output of fibady_token after the Quickstart}\" &&\nprintf 'header = \"Authorization: Bearer %s\"\\n' \"$FIBADY_ACCESS_TOKEN\" \\\n  | curl -sS -K - 'https://public-api.fibady.com/v1/companies/01K8EXAMP0C0MPANY000000001/partners/batch' \\\n    -H 'Content-Type: application/json' \\\n    -H 'Idempotency-Key: example-partners-batch-0001' \\\n    --data-binary '{\n      \"items\": [\n        {\n          \"externalId\": \"example-partner-0001\",\n          \"type\": \"client\",\n          \"name\": \"Example Customer\",\n          \"taxId\": \"99999999R\",\n          \"isCompany\": false,\n          \"email\": \"billing@example.com\",\n          \"country\": \"ES\",\n          \"paymentTermsDays\": 30\n        },\n        {\n          \"externalId\": \"example-partner-0002\",\n          \"type\": \"supplier\",\n          \"name\": \"Example Supplies GmbH\",\n          \"isCompany\": true,\n          \"email\": \"invoices@example.com\",\n          \"country\": \"DE\",\n          \"accountNumber\": \"DE89370400440532013000\"\n        }\n      ]\n    }'"}]}},"/v1/companies/{companyId}/runs/{runId}":{"get":{"operationId":"getRun","summary":"Get a run","description":"The run of a write to this company: its status, its per-record results once it has finished, whether what it changed is already in the reports (`reportsVisibility`) and whether it is already in the import history (`postProcessing`). Any client of the account with access to the company can read it, not only the one that created it. Poll it with backoff until `status` is `succeeded`, `partial` or `failed`; any other value, including one this client does not know, means poll again.\n\nA `runId` that does not exist, belongs to another company or is malformed gives the same 404.\n\nEvery response carries `X-Request-Id`, `Cache-Control: no-store` and `Pragma: no-cache`. Only the 429 and the 503 carry `Retry-After`. Error messages follow `Accept-Language` (English by default).","tags":["Runs"],"responses":{"200":{"description":"The run.","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicApiRun"},"examples":{"in_progress":{"summary":"A run still loading","value":{"runId":"01K8EXAMP0RVN0000000000001","entity":"journal-entries","operation":"upsert","status":"in_progress","startedAt":"2026-10-05T09:30:00.000Z","finishedAt":null,"errorClass":null,"reportsVisibility":"pending","postProcessing":"pending","counts":null,"results":null}},"succeeded":{"summary":"A finished run","value":{"runId":"01K8EXAMP0RVN0000000000001","entity":"journal-entries","operation":"upsert","status":"succeeded","startedAt":"2026-10-05T09:30:00.000Z","finishedAt":"2026-10-05T09:30:02.000Z","errorClass":null,"reportsVisibility":"visible","postProcessing":"done","counts":{"created":2,"updated":0,"unchanged":0,"unprojectable":0},"results":[{"index":0,"externalId":"example-journal-entry-0001","outcome":"created","internalId":"00000000-0000-4000-8000-000000000001"},{"index":1,"externalId":"example-journal-entry-0002","outcome":"created","internalId":"00000000-0000-4000-8000-000000000002"}]}}}}}},"400":{"description":"`api_invalid_query`: the operation takes no query parameters, and one was sent.","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseWithCode"}}}},"401":{"description":"`api_token_missing` (no `Authorization: Bearer` header) or `api_token_invalid` (unknown, expired or revoked access credential, or an inactive client). Carries `WWW-Authenticate`. Request a new token.","headers":{"WWW-Authenticate":{"schema":{"type":"string"}},"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseWithCode"}}}},"402":{"description":"`auth_invalid_or_expired_license`: the account's license is not valid.","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseWithCode"}}}},"403":{"description":"`api_origin_forbidden` (the request did not come through the public API endpoint), `api_insufficient_scope` (the client lacks `ingestion:read`; carries `WWW-Authenticate`), `api_company_not_allowed` (the client cannot access the company, whether it exists or not) or `api_company_param_conflict` (a `companyId` query parameter that does not match the path).","headers":{"WWW-Authenticate":{"description":"Only with `api_insufficient_scope`.","schema":{"type":"string"}},"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseWithCode"}}}},"404":{"description":"`api_resource_not_found`: there is no such run for this company. The same response for a `runId` that does not exist, belongs to another company or is malformed.","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseWithCode"}}}},"429":{"description":"`api_rate_limited`: too many requests for the account or the client. Retry after `Retry-After` seconds.","headers":{"Retry-After":{"$ref":"#/components/headers/RetryAfter"},"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseWithCode"},"examples":{"api_rate_limited":{"$ref":"#/components/examples/PublicApiRateLimited"}}}}},"500":{"description":"`internal_server_error`: unexpected failure. Safe to retry with backoff.","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseWithCode"}}}},"503":{"description":"`public_api_disabled` (the public API is not enabled for the client's account, during the invitation-only preview, or has been switched off) or `api_temporarily_unavailable` (a dependency failed). Retry `api_temporarily_unavailable` after `Retry-After` seconds. Do not retry `public_api_disabled` at that pace: back off exponentially, with at most 5 minutes between attempts, or stop and contact Fibady quoting `X-Request-Id`.","headers":{"Retry-After":{"$ref":"#/components/headers/RetryAfter"},"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseWithCode"}}}}},"security":[{"oauth2":["ingestion:read"]}],"parameters":[{"name":"companyId","in":"path","required":true,"description":"Company the run belongs to. It must be one the client can access.","schema":{"type":"string"}},{"name":"runId","in":"path","required":true,"description":"The `runId` returned by the write that created the run.","schema":{"type":"string"}}],"x-codeSamples":[{"lang":"Shell","label":"curl","source":": \"${FIBADY_ACCESS_TOKEN:?set it to the access_token of POST /v1/oauth/token, or to the output of fibady_token after the Quickstart}\" &&\nprintf 'header = \"Authorization: Bearer %s\"\\n' \"$FIBADY_ACCESS_TOKEN\" \\\n  | curl -sS -K - 'https://public-api.fibady.com/v1/companies/01K8EXAMP0C0MPANY000000001/runs/01K8EXAMP0RVN0000000000001'"}]}},"/v1/oauth/token":{"post":{"operationId":"issueAccessToken","summary":"Issue an access token","description":"Follows RFC 6749 §4.4. The body is `application/x-www-form-urlencoded` (at most 8 KiB), NOT JSON, and errors use the RFC's `error` / `error_description` fields rather than the product's `{error, code}` envelope.\n\nThe client authenticates with `client_secret_basic` (the `Authorization: Basic` header, RFC 6749 §2.3.1) or `client_secret_post` (`client_id` + `client_secret` in the body), never both. During a rotation the previous secret is accepted until its expiry.\n\nEvery response carries `X-Request-Id`, `Cache-Control: no-store` and `Pragma: no-cache`. Only the 429 and the 503 carry `Retry-After`. At most 60 tokens per client and hour; a token lasts 10 minutes, so request a new one when it expires instead of one per call.","tags":["Access tokens"],"responses":{"200":{"description":"Access token issued.","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicApiTokenResponse"},"examples":{"access_token":{"summary":"A token for a client with three scopes","value":{"access_token":"fpt_example-not-a-real-token","token_type":"Bearer","expires_in":600,"scope":"companies:read accounting:read accounting:write"}}}}}},"400":{"description":"`invalid_request` (wrong `Content-Type`, body over 8 KiB, missing `grant_type`, both authentication methods, mismatched `client_id`) or `unsupported_grant_type`.","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicApiTokenError"}}}},"401":{"description":"`invalid_client`: unknown, inactive or revoked client, wrong secret, no client authentication, or a malformed `Authorization: Basic` header. When the client tried Basic the response carries `WWW-Authenticate: Basic realm=\"fibady-public-api\"`.","headers":{"WWW-Authenticate":{"schema":{"type":"string"}},"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicApiTokenError"}}}},"403":{"description":"`origin_forbidden`: the request did not come through the public API endpoint.","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicApiTokenError"}}}},"429":{"description":"`rate_limited`: the client exceeded its tokens per hour. Retry after `Retry-After` seconds.","headers":{"Retry-After":{"$ref":"#/components/headers/RetryAfter"},"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicApiTokenError"}}}},"500":{"description":"`server_error`: unexpected failure. Safe to retry with backoff.","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicApiTokenError"}}}},"503":{"description":"`temporarily_unavailable`: the public API is not available for the client's account, or a dependency failed. Wait at least `Retry-After` seconds and retry; if it persists for more than 5 attempts in a row, the API is not enabled for the account: back off exponentially, with at most 5 minutes between attempts, or stop and contact Fibady quoting `X-Request-Id`.","headers":{"Retry-After":{"$ref":"#/components/headers/RetryAfter"},"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicApiTokenError"}}}}},"security":[],"requestBody":{"required":true,"content":{"application/x-www-form-urlencoded":{"schema":{"type":"object","properties":{"grant_type":{"type":"string","enum":["client_credentials"]},"client_id":{"type":"string","description":"`client_secret_post` only. With `Authorization: Basic` it may be omitted; if sent it must match the header's."},"client_secret":{"type":"string","description":"`client_secret_post` only. Mutually exclusive with `Authorization: Basic`."}},"required":["grant_type"]}}}},"x-codeSamples":[{"lang":"Shell","label":"curl","source":": \"${FIBADY_CLIENT_SECRET:?set it to your client secret, read without echo as in step 2 of the Quickstart}\" &&\nprintf 'user = \"%s:%s\"\\n' 'fapi_ExampleClientId0000000' \"$FIBADY_CLIENT_SECRET\" \\\n  | curl -sS -K - -d grant_type=client_credentials 'https://public-api.fibady.com/v1/oauth/token'"}]}},"/v1/openapi.json":{"get":{"operationId":"getOpenApiDocument","summary":"Get the OpenAPI document","description":"The machine-readable contract of every `/v1` operation: this same document. It needs no credentials and is served whenever the public API is enabled in the environment, whether or not your account is admitted yet.\n\nEvery response carries `X-Request-Id`, `Cache-Control: no-store` and `Pragma: no-cache`. Only the 503 carries `Retry-After`.","tags":["OpenAPI document"],"responses":{"200":{"description":"The OpenAPI document.","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicApiOpenApiDocument"}}}},"403":{"description":"`api_origin_forbidden`: the request did not come through the public API endpoint.","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseWithCode"}}}},"503":{"description":"`public_api_disabled` (the public API is switched off in this environment) or `api_temporarily_unavailable` (a dependency failed). Retry `api_temporarily_unavailable` after `Retry-After` seconds. Do not retry `public_api_disabled` at that pace: back off exponentially, with at most 5 minutes between attempts, or stop and contact Fibady quoting `X-Request-Id`.","headers":{"Retry-After":{"$ref":"#/components/headers/RetryAfter"},"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponseWithCode"}}}}},"security":[],"x-codeSamples":[{"lang":"Shell","label":"curl","source":"curl -sS 'https://public-api.fibady.com/v1/openapi.json'"}]}}},"components":{"schemas":{"ErrorResponseWithCode":{"type":"object","description":"Error of a resource operation. Branch on `code`, never on `error`: `error` is a human-readable message in the language of `Accept-Language` (English by default).","required":["error","code"],"properties":{"error":{"type":"string"},"code":{"type":"string","description":"Machine-readable error code: branch on it. The description of each response lists the codes it can carry."}}},"PublicApiErrorWithDetails":{"type":"object","required":["error","code"],"properties":{"error":{"type":"string"},"code":{"type":"string"},"details":{"type":"object","description":"Structured context of the error. Its keys depend on `code` and are listed in the description of each response; absent for codes that carry no context."}}},"PublicApiCompanySource":{"type":"object","description":"Whether the client could load this kind of data for the company through the public API.","properties":{"type":{"type":"string","enum":["journal_entries","invoices","bank"]},"kinds":{"type":"array","description":"Kinds of the sources assigned to this data type, without duplicates and sorted. `api` is a source fed by the public API.","items":{"type":"string","enum":["api","connector","erp","manual"]}},"writable":{"type":"boolean","description":"`true` when an `api` source is assigned and the client has the write scope of the type (`accounting:write`, `invoicing:write` or `banking:write`)."},"reason":{"type":["string","null"],"description":"Why it is not writable, the first that applies: no source assigned, no `api` source, or the client lacks the write scope. `null` when `writable`.","enum":["source_not_assigned","source_not_api","missing_scope",null]}},"required":["type","kinds","writable","reason"]},"PublicApiCompanyCatalog":{"type":"object","description":"Whether the client could write one catalog of the company through the public API. `reason` is `null` when `writable`; otherwise it is the first that applies: `source_not_assigned` (no source is assigned to the data the catalog depends on), `source_not_api` (a source is assigned, and none is an `api` source), `erp_api_mix_not_supported` (journal entries and invoices: one is fed by the API and the other by an ERP connector) or `missing_scope` (the client lacks the write scope of the catalog).","properties":{"writable":{"type":"boolean","description":"`true` when a batch of this catalog would pass the source check and the client has the write scope of the catalog. It says nothing about the validation of the records, concurrent batches or rate limits."},"reason":{"type":["string","null"],"description":"Closed per version; may gain new values in `/v1`, never lose or rename them. Show it as is; do not branch on it.","enum":["source_not_assigned","source_not_api","erp_api_mix_not_supported","missing_scope",null],"x-extensible-enum":["source_not_assigned","source_not_api","erp_api_mix_not_supported","missing_scope",null]}},"required":["writable","reason"]},"PublicApiCompany":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"currency":{"type":"string","description":"Functional currency of the company (ISO 4217)."},"lastClosedMonth":{"type":["string","null"],"pattern":"^\\d{4}-\\d{2}$","description":"Last closed month (`YYYY-MM`), or `null` if none."},"sources":{"type":"array","description":"One entry per data type, always the three, in this order.","items":{"$ref":"#/components/schemas/PublicApiCompanySource"}},"catalogs":{"type":"object","description":"Whether the client could write each catalog of the company through the public API, always the four keys. `accounts`, `partners` and `analyticValues` depend on the sources of journal entries and invoices: at least one of the two must be an `api` source, and neither may be fed by an ERP connector. `bankAccounts` depends on the `bank` source alone, like the `bank` entry of `sources`. The write scopes are `accounting:write` (`accounts` and `analyticValues`), `invoicing:write` (`partners`) and `banking:write` (`bankAccounts`). A `reason` about the sources is the `code` (`erp_api_mix_not_supported`) or the `details.reason` (`source_not_assigned`, `source_not_api`) of the 409 a batch of that catalog would get; a client without the write scope gets the 403 `api_insufficient_scope` first. It reflects the sources assigned when the request is served: if they change afterwards, the response of the batch is the one that counts.","properties":{"accounts":{"$ref":"#/components/schemas/PublicApiCompanyCatalog"},"partners":{"$ref":"#/components/schemas/PublicApiCompanyCatalog"},"analyticValues":{"$ref":"#/components/schemas/PublicApiCompanyCatalog"},"bankAccounts":{"$ref":"#/components/schemas/PublicApiCompanyCatalog"}},"required":["accounts","partners","analyticValues","bankAccounts"]}},"required":["id","name","currency","lastClosedMonth","sources","catalogs"]},"PublicApiCompanyPage":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/PublicApiCompany"}},"nextCursor":{"type":["string","null"],"description":"Opaque cursor for the next page, or `null` on the last one. A page may be shorter than `limit`, even empty, with a non-null `nextCursor`."}},"required":["items","nextCursor"]},"PublicApiAccount":{"type":"object","description":"One live account of the company's chart of accounts, as stored.","properties":{"code":{"type":"string","description":"Account code, exactly as stored: it is not trimmed, so it may carry leading or trailing spaces when another source loaded it that way. Unique among the live accounts of the company."},"name":{"type":"string"},"origin":{"type":"string","description":"The source that wrote the account last: `manual`, `csv`, `api`, the name of a connector (`odoo`, `holded`) or another value. An open string, not an enum: new values appear with new sources."},"internalId":{"type":"string","description":"Fibady identifier of the account."},"classificationHint":{"type":["string","null"],"description":"Free text that helps classify the account, or `null` if none."},"flowTypeHint":{"type":["string","null"],"description":"Free text that helps classify the account's cash flow, or `null` if none."}},"required":["code","name","origin","internalId","classificationHint","flowTypeHint"]},"PublicApiAccountPage":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/PublicApiAccount"}},"nextCursor":{"type":["string","null"],"description":"Opaque cursor for the next page, or `null` on the last one. A page is shorter than `limit` only when it is the last one."}},"required":["items","nextCursor"]},"PublicApiRun":{"type":"object","description":"State of one write batch (a run). `status` goes from `in_progress` to `succeeded`, `partial` (some records are `unprojectable`) or `failed`. `counts` and `results` are those of the write response and are `null` while the run is `in_progress` and when it `failed`. `finishedAt` is `null` while `in_progress`. `errorClass` is `null` unless the run `failed`, and is never a message.\n\n`operation` tells which write created the run. An `upsert` run counts `created`, `updated`, `unchanged` and `unprojectable`. A `delete` run (a `batch-delete`) counts `deleted` and `notFound`, carries the four upsert counts at 0, and its `results` say `deleted` or `not_found`; the 200 of the `batch-delete` itself publishes only `deleted` and `notFound`.\n\n`reportsVisibility` and `postProcessing` are computed on every request. `reportsVisibility: visible` means that what THIS batch changed is already in the reports; an `unchanged` record changed nothing in this batch, so its visibility is that of the run that wrote it. `pending` may last. `postProcessing` is `done` once the run is in the import history.","properties":{"runId":{"type":"string","description":"Identifier of the run, as returned by the write that created it."},"entity":{"type":"string","description":"Closed per version; may gain new values in `/v1`, never lose or rename them. An unknown value is an entity or operation this client does not know: ignore the run.","enum":["accounts","analytic-values","partners","bank-accounts","journal-entries","invoices","bank-movements","invoice-payments"],"x-extensible-enum":["accounts","analytic-values","partners","bank-accounts","journal-entries","invoices","bank-movements","invoice-payments"]},"operation":{"type":"string","description":"Closed per version; may gain new values in `/v1`, never lose or rename them. An unknown value is an entity or operation this client does not know: ignore the run.","enum":["upsert","delete"],"x-extensible-enum":["upsert","delete"]},"status":{"type":"string","description":"Closed per version; may gain new values in `/v1`, never lose or rename them. An unknown value is NOT terminal: poll again.","enum":["in_progress","succeeded","partial","failed"],"x-extensible-enum":["in_progress","succeeded","partial","failed"]},"startedAt":{"type":"string","format":"date-time","description":"When the current attempt started. A retry with the same `Idempotency-Key` restarts it."},"finishedAt":{"type":["string","null"],"format":"date-time","description":"When the run finished, or `null` while it is `in_progress`."},"errorClass":{"type":["string","null"],"description":"Closed per version; may gain new values in `/v1`, never lose or rename them. Treat an unknown value as a generic error, the same as `internal`.","enum":["timeout","unavailable","internal","stale_run",null],"x-extensible-enum":["timeout","unavailable","internal","stale_run",null]},"reportsVisibility":{"type":"string","description":"Closed per version; may gain new values in `/v1`, never lose or rename them. An unknown value is NOT terminal: poll again.","enum":["pending","visible"],"x-extensible-enum":["pending","visible"]},"postProcessing":{"type":"string","description":"Closed per version; may gain new values in `/v1`, never lose or rename them. An unknown value is NOT terminal: poll again.","enum":["pending","done","failed"],"x-extensible-enum":["pending","done","failed"]},"counts":{"type":["object","null"],"description":"Records of the batch by outcome, or `null` while `in_progress` and when `failed`. Every run carries the four upsert counts; a `delete` run carries them at 0, plus `deleted` and `notFound`, which an `upsert` run does not carry.","properties":{"created":{"type":"integer","minimum":0},"updated":{"type":"integer","minimum":0},"unchanged":{"type":"integer","minimum":0},"unprojectable":{"type":"integer","minimum":0},"deleted":{"type":"integer","minimum":0},"notFound":{"type":"integer","minimum":0}},"required":["created","updated","unchanged","unprojectable"]},"results":{"type":["array","null"],"description":"One entry per record of the batch, in request order, or `null` while `in_progress` and when `failed`.","items":{"type":"object","properties":{"index":{"type":"integer","minimum":0,"description":"Position of the record in the request."},"externalId":{"type":"string","description":"Identity of the record, as sent in the request."},"outcome":{"type":"string","description":"Closed per version; may gain new values in `/v1`, never lose or rename them. Treat an unknown value as an unconfirmed result: check the record before retrying it.","enum":["created","updated","unchanged","unprojectable","deleted","not_found"],"x-extensible-enum":["created","updated","unchanged","unprojectable","deleted","not_found"]},"internalId":{"type":["string","null"],"description":"Fibady identifier of the record. `null` only for an `unprojectable` record that was never written and, in a `delete` run, for a `not_found` record and for a `deleted` one that had been accepted but never written."},"code":{"type":"string","description":"Closed per version; may gain new values in `/v1`, never lose or rename them. Treat an unknown value as `other`.","enum":["missing_account","missing_bank_account","missing_date","bank_account_unlinked","missing_source_id","bank_account_name_exhausted","missing_parent","other"],"x-extensible-enum":["missing_account","missing_bank_account","missing_date","bank_account_unlinked","missing_source_id","bank_account_name_exhausted","missing_parent","other"]},"warnings":{"type":"array","description":"Present only when the record has warnings. They do not block it.","items":{"type":"string","description":"Closed per version; may gain new values in `/v1`, never lose or rename them. Treat an unknown value as a generic warning: it does not block the record.","enum":["date_in_closed_period","date_far_future","fx_rate_missing","entry_type_not_applied","before_opening_balance_date"],"x-extensible-enum":["date_in_closed_period","date_far_future","fx_rate_missing","entry_type_not_applied","before_opening_balance_date"]}}},"required":["index","externalId","outcome","internalId"]}}},"required":["runId","entity","operation","status","startedAt","finishedAt","errorClass","reportsVisibility","postProcessing","counts","results"]},"PublicApiRecordStatus":{"type":"object","description":"State of one record written through the API, looked up by the `externalId` it was written with. `state` is `projected` when the record has a Fibady identifier (`internalId`), and `pending` when it was received but never applied: it came back `unprojectable` in its batch (the reason is in `results[].code` of that run), or its batch did not finish. A `pending` record becomes `projected` when a later write of the same `externalId` applies it.\n\n`projected` says the record exists, not that the last version sent is the one applied: a later write of the same record that came back `unprojectable` leaves it `projected`, with the same `internalId`.","properties":{"externalId":{"type":"string","description":"The identifier the record was written with."},"state":{"type":"string","description":"Closed per version; may gain new values in `/v1`, never lose or rename them. An unknown value is NOT terminal: poll again.","enum":["projected","pending"],"x-extensible-enum":["projected","pending"]},"internalId":{"type":["string","null"],"description":"Fibady identifier of the record, or `null` while it is `pending`."}},"required":["externalId","state","internalId"]},"PublicApiRecordListItem":{"description":"One record of a list of what was written through the API: its state, as `GET …/{externalId}` returns it, plus `updatedAt`.","allOf":[{"$ref":"#/components/schemas/PublicApiRecordStatus"},{"type":"object","properties":{"updatedAt":{"type":"string","format":"date-time","description":"Last time a write through the API changed the record in Fibady (its content, or when it was applied), in UTC with microseconds. It does not move when the record is edited inside Fibady."}},"required":["updatedAt"]}]},"PublicApiRecordPage":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/PublicApiRecordListItem"}},"nextCursor":{"type":["string","null"],"description":"Opaque cursor for the next page, or `null` on the last one. A page may be shorter than `limit`, even empty, with a non-null `nextCursor`: keep following it."},"asOf":{"type":"string","format":"date-time","description":"The same in every page of a pass, in UTC with microseconds. Start the next pass with `updatedSince` set to this value: a record changed after it is listed again, never missed."}},"required":["items","nextCursor","asOf"]},"PublicApiValidationError":{"type":"object","description":"A 422 of a write. With `validation_failed`, `details` lists up to 100 problems, sorted by item index and then by pointer, and NOTHING of the batch was written: fix them and send the whole batch again. With `idempotency_key_reused` there is no `details`: the `Idempotency-Key` was already used with a different body.","properties":{"error":{"type":"string","description":"Human-readable message, in the language of `Accept-Language`."},"code":{"type":"string","description":"`validation_failed` or `idempotency_key_reused`."},"details":{"type":"array","maxItems":100,"description":"Only with `validation_failed`.","items":{"type":"object","properties":{"pointer":{"type":"string","description":"JSON Pointer (RFC 6901) to the offending value in the request body, such as `/items/3/name`. Empty for a problem that is not in the body (a malformed `Idempotency-Key`)."},"code":{"type":"string","description":"Closed per version; may gain new values in `/v1`, never lose or rename them. Treat an unknown value as a generic validation error: the record was not written.","enum":["duplicate_external_id","currency_not_functional","unbalanced_entry","before_history_start","invalid_amount","invalid_date","invalid_enum","required_field","unknown_field","invalid_currency","unknown_reference","line_count_out_of_range","bank_movement_account_change","analytic_allocation_over_100","analytic_dimension_limit_reached","invalid_external_id","invalid_idempotency_key","invoice_not_payable","invoice_payment_invoice_change","expected_deletes_mismatch"],"x-extensible-enum":["duplicate_external_id","currency_not_functional","unbalanced_entry","before_history_start","invalid_amount","invalid_date","invalid_enum","required_field","unknown_field","invalid_currency","unknown_reference","line_count_out_of_range","bank_movement_account_change","analytic_allocation_over_100","analytic_dimension_limit_reached","invalid_external_id","invalid_idempotency_key","invoice_not_payable","invoice_payment_invoice_change","expected_deletes_mismatch"]}},"required":["pointer","code"]}}},"required":["error","code"]},"PublicApiBatchResult":{"description":"The 200 of a write batch, in one of two shapes.\n\nA real write returns the run: `runId`, `status` (`succeeded`, or `partial` when some records are `unprojectable`), `counts` and one `results` entry per record, in request order. `reportsVisibility` says whether what THIS batch changed is already in the reports; `pending` may last, and `GET /v1/companies/{companyId}/runs/{runId}` tells when it turns `visible`. An `unchanged` record changed nothing in this batch, so its visibility is that of the run that wrote it. `postProcessing` is `pending` until the run reaches the import history.\n\nWith `dryRun=true` nothing is written and there is no run: the response is `dryRun: true` and one `valid` entry per record.","oneOf":[{"type":"object","description":"A real write. The batch was loaded and has a run.","properties":{"runId":{"type":"string","description":"Identifier of the run. Use it with `GET /v1/companies/{companyId}/runs/{runId}`."},"status":{"type":"string","description":"Closed per version; may gain new values in `/v1`, never lose or rename them. An unknown value is NOT terminal: poll again.","enum":["in_progress","succeeded","partial","failed"],"x-extensible-enum":["in_progress","succeeded","partial","failed"]},"counts":{"type":"object","description":"Records of the batch by outcome.","properties":{"created":{"type":"integer","minimum":0},"updated":{"type":"integer","minimum":0},"unchanged":{"type":"integer","minimum":0},"unprojectable":{"type":"integer","minimum":0}},"required":["created","updated","unchanged","unprojectable"]},"results":{"type":"array","description":"One entry per record of the batch, in request order.","items":{"type":"object","properties":{"index":{"type":"integer","minimum":0,"description":"Position of the record in the request."},"externalId":{"type":"string","description":"Identity of the record, as sent in the request."},"outcome":{"type":"string","description":"Closed per version; may gain new values in `/v1`, never lose or rename them. Treat an unknown value as an unconfirmed result: check the record before retrying it.","enum":["created","updated","unchanged","unprojectable"],"x-extensible-enum":["created","updated","unchanged","unprojectable"]},"internalId":{"type":["string","null"],"description":"Fibady identifier of the record. `null` only for an `unprojectable` record that was never written."},"code":{"type":"string","description":"Closed per version; may gain new values in `/v1`, never lose or rename them. Treat an unknown value as `other`.","enum":["missing_account","missing_bank_account","missing_date","bank_account_unlinked","missing_source_id","bank_account_name_exhausted","missing_parent","other"],"x-extensible-enum":["missing_account","missing_bank_account","missing_date","bank_account_unlinked","missing_source_id","bank_account_name_exhausted","missing_parent","other"]},"warnings":{"type":"array","description":"Present only when the record has warnings. They do not block it.","items":{"type":"string","description":"Closed per version; may gain new values in `/v1`, never lose or rename them. Treat an unknown value as a generic warning: it does not block the record.","enum":["date_in_closed_period","date_far_future","fx_rate_missing","entry_type_not_applied","before_opening_balance_date"],"x-extensible-enum":["date_in_closed_period","date_far_future","fx_rate_missing","entry_type_not_applied","before_opening_balance_date"]}}},"required":["index","externalId","outcome","internalId"]}},"reportsVisibility":{"type":"string","description":"Closed per version; may gain new values in `/v1`, never lose or rename them. An unknown value is NOT terminal: poll again.","enum":["pending","visible"],"x-extensible-enum":["pending","visible"]},"postProcessing":{"type":"string","description":"Closed per version; may gain new values in `/v1`, never lose or rename them. An unknown value is NOT terminal: poll again.","enum":["pending","done","failed"],"x-extensible-enum":["pending","done","failed"]}},"required":["runId","status","counts","results","reportsVisibility","postProcessing"]},{"type":"object","description":"A `dryRun=true` validation. Nothing was written and there is no run.","properties":{"dryRun":{"type":"boolean","description":"Always `true`.","enum":[true]},"results":{"type":"array","description":"One entry per record of the batch, in request order.","items":{"type":"object","properties":{"index":{"type":"integer","minimum":0,"description":"Position of the record in the request."},"externalId":{"type":"string","description":"Identity of the record, as sent in the request."},"outcome":{"type":"string","description":"Closed per version; may gain new values in `/v1`, never lose or rename them. Treat an unknown value as an unconfirmed result: check the record before retrying it.","enum":["valid"],"x-extensible-enum":["valid"]},"warnings":{"type":"array","description":"Present only when the record has warnings. They do not block it.","items":{"type":"string","description":"Closed per version; may gain new values in `/v1`, never lose or rename them. Treat an unknown value as a generic warning: it does not block the record.","enum":["date_in_closed_period","date_far_future","fx_rate_missing","entry_type_not_applied","before_opening_balance_date"],"x-extensible-enum":["date_in_closed_period","date_far_future","fx_rate_missing","entry_type_not_applied","before_opening_balance_date"]}}},"required":["index","externalId","outcome"]}}},"required":["dryRun","results"]}]},"PublicApiDeleteBatchResult":{"type":"object","description":"The 200 of a real `batch-delete`: the run of the deletion. `status` is `succeeded`, `counts` says how many records were deleted and how many were not found, and there is one `results` entry per requested `externalId`, in request order. `reportsVisibility` says whether what THIS deletion changed is already out of the reports; `pending` may last, and `GET /v1/companies/{companyId}/runs/{runId}` tells when it turns `visible`. `postProcessing` is `pending` until the run reaches the import history.","properties":{"runId":{"type":"string","description":"Identifier of the run. Use it with `GET /v1/companies/{companyId}/runs/{runId}`."},"status":{"type":"string","description":"Closed per version; may gain new values in `/v1`, never lose or rename them. An unknown value is NOT terminal: poll again.","enum":["in_progress","succeeded","partial","failed"],"x-extensible-enum":["in_progress","succeeded","partial","failed"]},"counts":{"type":"object","description":"Requested records by outcome: `deleted` and `notFound`. May gain new keys in `/v1`.","properties":{"deleted":{"type":"integer","minimum":0},"notFound":{"type":"integer","minimum":0}},"required":["deleted","notFound"]},"results":{"type":"array","description":"One entry per requested `externalId`, in request order.","items":{"type":"object","properties":{"index":{"type":"integer","minimum":0,"description":"Position of the record in the request."},"externalId":{"type":"string","description":"Identity of the record, as sent in the request."},"outcome":{"type":"string","description":"Closed per version; may gain new values in `/v1`, never lose or rename them. Treat an unknown value as an unconfirmed result: check the record before retrying it.","enum":["deleted","not_found"],"x-extensible-enum":["deleted","not_found"]},"internalId":{"type":["string","null"],"description":"Fibady identifier of the deleted record. `null` for a `not_found` record, and for a `deleted` one that had been accepted but never written."},"warnings":{"type":"array","description":"Present only when the record has warnings. They do not block it.","items":{"type":"string","description":"Closed per version; may gain new values in `/v1`, never lose or rename them. Treat an unknown value as a generic warning: it does not block the record.","enum":["date_in_closed_period","date_far_future","fx_rate_missing","entry_type_not_applied","before_opening_balance_date"],"x-extensible-enum":["date_in_closed_period","date_far_future","fx_rate_missing","entry_type_not_applied","before_opening_balance_date"]}}},"required":["index","externalId","outcome","internalId"]}},"reportsVisibility":{"type":"string","description":"Closed per version; may gain new values in `/v1`, never lose or rename them. An unknown value is NOT terminal: poll again.","enum":["pending","visible"],"x-extensible-enum":["pending","visible"]},"postProcessing":{"type":"string","description":"Closed per version; may gain new values in `/v1`, never lose or rename them. An unknown value is NOT terminal: poll again.","enum":["pending","done","failed"],"x-extensible-enum":["pending","done","failed"]}},"required":["runId","status","counts","results","reportsVisibility","postProcessing"]},"PublicApiDeleteDryRunResult":{"type":"object","description":"The 200 of a `batch-delete` with `dryRun=true`: what the deletion would do. Nothing was deleted and there is no run. One `results` entry per requested `externalId`, in request order.","properties":{"dryRun":{"type":"boolean","description":"Always `true`.","enum":[true]},"results":{"type":"array","description":"One entry per requested `externalId`, in request order.","items":{"type":"object","properties":{"index":{"type":"integer","minimum":0,"description":"Position of the record in the request."},"externalId":{"type":"string","description":"Identity of the record, as sent in the request."},"outcome":{"type":"string","description":"Closed per version; may gain new values in `/v1`, never lose or rename them. Treat an unknown value as an unconfirmed result: check the record before retrying it.","enum":["would_delete","not_found"],"x-extensible-enum":["would_delete","not_found"]},"internalId":{"type":["string","null"],"description":"Fibady identifier of the record that would be deleted. `null` for a `not_found` record, and for one that was accepted but never written."},"warnings":{"type":"array","description":"Present only when the record has warnings. They do not block it.","items":{"type":"string","description":"Closed per version; may gain new values in `/v1`, never lose or rename them. Treat an unknown value as a generic warning: it does not block the record.","enum":["date_in_closed_period","date_far_future","fx_rate_missing","entry_type_not_applied","before_opening_balance_date"],"x-extensible-enum":["date_in_closed_period","date_far_future","fx_rate_missing","entry_type_not_applied","before_opening_balance_date"]}}},"required":["index","externalId","outcome","internalId"]}}},"required":["dryRun","results"]},"PublicApiTokenResponse":{"type":"object","description":"Access token of the public API (RFC 6749 §5.1). No refresh token.","properties":{"access_token":{"type":"string","description":"Opaque bearer token (`fpt_…`). Send it as `Authorization: Bearer`."},"token_type":{"type":"string","enum":["Bearer"]},"expires_in":{"type":"integer","description":"Lifetime in seconds (600)."},"scope":{"type":"string","description":"Space-separated scopes of the client."}},"required":["access_token","token_type","expires_in","scope"]},"PublicApiTokenError":{"type":"object","description":"RFC 6749 §5.2 error. `error_description` is a fixed English text per `error`; it never echoes the request.","properties":{"error":{"type":"string","enum":["invalid_request","invalid_client","unsupported_grant_type","origin_forbidden","rate_limited","server_error","temporarily_unavailable"]},"error_description":{"type":"string"}},"required":["error","error_description"]},"PublicApiOpenApiDocument":{"type":"object","description":"The OpenAPI 3.1 document of the public API, this same document.","properties":{"openapi":{"type":"string","description":"OpenAPI version (`3.1.x`)."},"info":{"type":"object"},"paths":{"type":"object"}},"required":["openapi","info","paths"]}},"examples":{"PublicApiRateLimited":{"summary":"Too many requests","value":{"error":"Too many requests. Wait for the indicated time and try again.","code":"api_rate_limited"}},"PublicApiValidationFailed":{"summary":"A record with a field the API does not accept","value":{"error":"The request is not valid and nothing was written. Check the details, fix the listed fields and send it again.","code":"validation_failed","details":[{"pointer":"/items/0/note","code":"unknown_field"}]}},"PublicApiBatchInProgress":{"summary":"Another batch of the company is loading","value":{"error":"Another batch is being loaded for this company. Wait for the indicated time and send yours again.","code":"batch_in_progress"}}},"headers":{"XRequestId":{"description":"Identifier of the request, on every response of the API, errors included: quote it when you contact Fibady. A 429 or 5xx without it did not come from the API but from the edge.","schema":{"type":"string"}},"RetryAfter":{"description":"How many seconds to wait, at least, before sending the request again. Except on a 503 `public_api_disabled`: the API is not enabled for the account, so do not retry at this pace; back off exponentially, with at most 5 minutes between attempts, or stop and contact Fibady.","schema":{"type":"integer","minimum":1}}},"securitySchemes":{"oauth2":{"type":"oauth2","description":"Public API access token (`fpt_…`) from `POST /v1/oauth/token` (client_credentials), sent as `Authorization: Bearer`.","flows":{"clientCredentials":{"tokenUrl":"https://public-api.fibady.com/v1/oauth/token","scopes":{"companies:read":"List the companies the client can access.","ingestion:read":"Read data loads.","accounting:read":"Read accounting data.","accounting:write":"Write accounting data.","accounting:delete":"Delete accounting data.","invoicing:read":"Read invoicing data.","invoicing:write":"Write invoicing data.","invoicing:delete":"Delete invoicing data.","banking:read":"Read banking data.","banking:write":"Write banking data.","banking:delete":"Delete banking data."}}}}}}}