Developers
Show batch ingestion and synchronization guides and endpoints, core concepts, and the content hash calculator.
Explore the docs

Updates and compatibility

Version history

Understand what changed, what it means for your integration, and what to do next.

Version 4.2.0

Release Date: Security Enforcements

Adds security enforcements, server-side money rounding, durable request replay, opaque pagination, unchanged-update detection, batch ingestion, and content-hash synchronization. Payload field set is unchanged. Enforcements can reject unclean requests; check payloads and clean them if failures happen.

API 4.2.0, documented 2026-09-14. Confirm availability in your target environment. The content-hash v2 creditor repair requires an operator-run backfill and refreshed sync-state caches; omitted update creditors remain preserved. Local hashing failures should send normal updates. Earlier entries describe historical behavior.

Updated: the API rounds money to cents

Amounts with more than two decimal places are accepted. For both POST /ingest and POST /ingest/batch, the API rounds each monetary field to the nearest cent, with halfway cases rounded away from zero, before storing the event, calculating derived amounts, comparing content hashes, or writing domain rows.

Payload compatibility: No new fields or payload structure are required. Existing valid cent-valued amounts keep their value. Clients no longer need to pre-round amounts to avoid a precision error. This replaces the earlier rejection rule and is not a breaking payload change.

  • Send finite JSON numbers, not numeric strings. Scientific notation is accepted. Rounding uses decimal arithmetic on the shortest decimal representation of the parsed JSON number, not binary multiplication or Math.round. Precision already lost while constructing or parsing a number cannot be recovered.
  • Rounded values and calculated amounts must fit NUMERIC(15,2): -9999999999999.99 to 9999999999999.99. Overflow still returns HTTP 400; extra decimal places alone do not.
  • Existing sign restrictions apply to the submitted value: a negative amount is still invalid for a nonnegative field, even if it would round to zero. Negative examples below only apply to fields that allow negative values. Other numeric business bounds are checked after rounding.
  • Ratios such as insurer_coverage, counts, and values nested inside info are not rounded as money. Omitted and nullable fields retain their existing behavior. Zero is normalized without a negative sign.
Normalized amounts are shown at two-decimal storage scale. Submit JSON numbers, not strings.
Submitted number Normalized amount
10.254 10.25
10.255 10.26
-10.255 -10.26
1.005 1.01
-0.004 0.00
1.005e1 10.05

Rounded fields: disbursement_amount, gross_value, upfront_fee, total_expected_margin, approved_amount, operation_revenue, operation_cost, asset_value, depreciation_amount, payment_total, payment_principal, payment_fee.

Derived amounts use rounded components

Components are rounded separately before calculation. With gross_value omitted, disbursement_amount: 10.005 plus upfront_fee: 10.005 produces gross_value: 20.02, not 20.01.

Same rounded value means the same content hash

Otherwise identical updates containing 10.251, 10.254, or 10.25 normalize to the same money value and content hash. With 10.25 already stored, they return 200 with action=unchanged. Hash version remains 2; this rounding update alone requires no hash-version change or backfill.

Same rounded value does not mean the same retry

Request idempotency still compares the original submitted content, not its rounded equivalent. Reusing one key with 10.251 and then 10.254 returns 409 IDEMPOTENCY_KEY_REUSED even though both round to 10.25. Preserve the original amount and key for an exact retry.

Security enforcements: can trigger failures

This is not a breaking change. These security enforcements can reject requests. Payload fields stay the same. If a request fails, clean the payload and retry.

  1. Retries no longer return a generic duplicate response

    Before
    Repeating an Idempotency-Key returned HTTP 200 with status=duplicate, even if the original request had a different result.
    Now
    An identical stored request replays its original HTTP status and response body. A successful create replays as 201, an unchanged update as 200, and a stored terminal processing error replays that error. JSON object-property order does not matter.
    If it fails
    Remove checks that require status=duplicate or expect every retry to return 200. Interpret the returned status/body normally. After a timeout or lost response, retry the same request with the same key; do not generate a new key just because the response was lost.
  2. An idempotency key cannot be reused for different content

    Before
    Reusing an existing key could return the generic duplicate response without reporting that the category, step, or payload had changed.
    Now
    Changed request content under a stored key returns HTTP 409 with error=IDEMPOTENCY_KEY_REUSED. Keys are tenant-scoped, case-sensitive, nonblank, and limited to 255 UTF-8 bytes. Stored keys have no cache-based expiry and remain reserved while their event records exist.
    If it fails
    Use one key per logical event and preserve it for retries. Use a new key for a changed request, including a correction to a terminally recorded failure. Never assume that waiting for a cache timeout makes a used key reusable. Validation failures before persistence and unexpected rolled-back failures are not stored terminal replays.
  3. Raw IDs and timestamps are no longer valid pagination cursors

    Before
    ID-list examples used the last business ID, such as client-002, as the next cursor. Row pagination could use a raw sort value.
    Now
    Read endpoints require the opaque token returned in pagination.next_cursor. It carries the sort value and any primary-key tiebreakers. A raw ID, timestamp, or malformed token returns HTTP 400 Invalid cursor.
    If it fails
    Discard stored legacy cursors and restart from the first page. URL-encode and reuse next_cursor without constructing or decoding it, keeping the same table, filters, and sort order. Stop when has_more is false. Row lists exclude records whose selected sort value is null.
  4. Unknown payload fields are rejected instead of ignored

    Before
    Extra envelope or category/step fields could pass validation. A permissive local validator was not evidence that the API supported those fields.
    Now
    Unknown envelope and data fields return HTTP 400. Single ingestion accepts category and data at the envelope level; allowed data fields depend on category and data.step. Sending a server-generated content_hash is also rejected.
    If it fails
    Send only declared fields for the selected category and step. Strip database/audit fields from outgoing records and use declared info metadata where supported. Keep explicitly supported legacy fields such as payment/operation entity_type and entity_id separate from genuinely unknown fields.
  5. An unchanged update returns 200 and produces no domain changes

    Before
    Accepted updates returned HTTP 201 and could rewrite the row even when its business content had not changed.
    Now
    The API merges a partial update with the current row and compares its content hash. An equal hash returns HTTP 200 with status=unchanged and action=unchanged. It does not change domain timestamps or generate child/history effects; an ingest event and response are still recorded.
    If it fails
    Treat 200 unchanged as success, not as an error or a reason to retry with a new key. Use action to distinguish created, updated, unchanged, and deleted. Do not rely on unchanged updates to refresh domain timestamps or trigger history rows. Creates and changed updates remain 201; deletes also return 201.
  6. Purchase payments must reference a linked purchase client

    Before
    A purchase payment needed client_id, but membership in the parent purchase allocation was not enforced.
    Now
    The payment client must be linked to the purchase through purchase_clients. An unlinked client returns HTTP 400 with a purchase payment client mismatch error. Loan payments continue to resolve the client from the parent loan.
    If it fails
    Create the client and establish its purchase_clients link before submitting the payment. An existing client record alone is not enough. Correct a rejected payment using a new idempotency key if the original failure was stored.
  7. Disbursements with active dependents cannot be deleted

    Before
    Deleting a disbursement did not enforce the current active-dependency guard.
    Now
    A delete returns HTTP 409 when active dependent records exist, including payments, operations, and applicable history or legacy dependencies. The API does not cascade the delete through those records.
    If it fails
    Check and resolve dependencies using supported workflows before retrying. Do not blindly delete financial history to bypass the guard; contact support for dependencies you cannot resolve. Treat this conflict differently from IDEMPOTENCY_KEY_REUSED.
  8. Request limits and tenant readiness can reject writes before processing

    Before
    Clients could rely on less restrictive request handling or on runtime schema setup. List limit values were less strictly bounded.
    Now
    Single and batch ingestion bodies are capped at 1 MiB; oversized bodies return 413. Table row/ID lists accept integer limits from 1 to 1000 (default 50); invalid limits return 400. Ingestion and sync-state return 503 SCHEMA_NOT_READY if tenant setup is incomplete.
    If it fails
    Enforce UTF-8 request size limits before sending. Split bulk work into bounded requests, not one oversized event, and validate list limits locally. Pause ingestion and contact support for SCHEMA_NOT_READY; repeated retries do not perform tenant setup.

Payload check

Required check: confirm no bad payload is formed. Enforcements can trigger failures. If a request fails, clean the payload and retry.

  1. Verify availability in your target environment and confirm tenant readiness. Check payloads locally so bad requests fail before production traffic.
  2. Update response handling first: accept normal 201 successes, 200 unchanged, exact stored replays, and per-item batch statuses.
  3. Persist retry keys per logical event; separate same-request retries from changed or corrected requests.
  4. Strip unknown fields and send finite numeric amounts; the API rounds money for you. If a request fails, clean the payload and retry. Use a new key only when the original failure was stored. Keep sign/range validation and key/body/page-size limits, but do not reject amounts solely for having extra decimals.
  5. Discard legacy cursors and restart pagination using only returned next_cursor values.
  6. Check purchase-client links and disbursement dependencies before writes or deletes.
  7. If computing content hashes locally, match the parsed-number rounding semantics and individually rounded derived amounts. This update keeps hash version 2; no backfill is needed for rounding alone. Otherwise retain ID-based reconciliation.
  8. Test halfway rounding, negative/sign-restricted values, derived sums, rounded-equivalent updates, and raw-amount key conflicts, alongside timeout/retry, overflow/unknown-field errors, paginated reads, and mixed-result batches before rollout.

Version 4.1.0

Release Date: New Read Endpoint

Added business-ID listing for reconciliation. This was an additive release with no breaking changes at the time.

ID-based reconciliation

  • Added GET /api/tables/{table}/ids to list existing business IDs using tenant API-key authentication and pagination.
  • Compare source IDs with stored IDs to choose create versus update without probing ingestion with duplicate creates.
  • Pagination subsequently changed in the September 2026 update: follow the opaque next_cursor contract documented above, not the original raw-ID cursor examples.

Version 4.0.0

Release Date: New API

New /ingest endpoint documentation plus read data endpoints for listing and retrieving scoped records.

Endpoints

  • Documented /ingest as the primary endpoint for event ingestion.
  • Added read data endpoints: GET /api/tables/{table} and GET /api/tables/{table}/{id}.
  • Documented allowed read tables for clients, disbursements, payments, operations, purchase links, status history, and derived event rows.
  • Clarified required Authorization and Idempotency-Key headers.
  • Updated response examples for success, duplicate idempotency, validation, authorization, and server errors.

Payloads

  • Focused docs on client, disbursement, payment, and optional operation categories.
  • Updated purchase disbursement examples to use purchase_clients and reject legacy client_ids.
  • Clarified payment source as optional with default client.
  • Clarified that all _date fields accept ISO 8601 date-only or timestamp input.
  • Documented purchase payment client_id requirement.
  • Documented worker-created disbursement and asset status side effects from accepted parent events.

Documentation UX

  • Added Start Here guide for request shape, category choice, and recommended event flow.
  • Reworked payload pages with field summaries before long explanations.
  • Kept version history available for migration context.

Migration Notes

  1. Send all new events to /ingest using category and data.
  2. Use /api/tables/{table} or /api/tables/{table}/{id} to read records scoped to your API key.
  3. Use disbursement.kind instead of old top-level loan or purchase categories.
  4. Use payment.status expected events instead of old schedule category.

Version 3.6.1

Release Date: Breaking Changes

Payment events require due_date, separating scheduled due dates from optional receipt timestamps.

Payment Events

  • Added due_date as a required ISO 8601 date field.
  • Kept payment_date as an optional actual receipt timestamp for paid payments.
  • Removed write_off and insurance from active examples, validator UI, and FAQ navigation.
  • Updated payment examples, payload reference, FAQ documentation, and validator logic.

Migration Notes

  1. Send due_date on every payment event using ISO 8601 date input, for example "2024-11-20".
  2. Continue using payment_date for actual payment receipt timestamp when status is paid.
  3. Use disbursement updates with change_date and change_reason for extension, rebate, and write-off derived rows.

Version 3.6.0

Release Date: Breaking Changes

Backend alignment release: synchronous ingest processing, four active top-level categories, disbursement_id child references, and legacy loan/purchase migration into disbursements.

Ingest Categories

  • Active /ingest categories are client, disbursement, payment, and operation.
  • Added operation documentation for revenue/cost data and optional asset fields.
  • Deprecated categories are documented as no longer accepted as top-level ingest categories.

References And Processing

  • payment requires disbursement_id; operation can also reference disbursement_id for underlying operation or asset tracking.
  • Deprecated entity_type and entity_id fields are tolerated but ignored by new processing.
  • API stores and processes events synchronously in the request path.

Migration Notes

  1. Keep loan and purchase semantics inside disbursement.kind.
  2. Send operation/asset source data through operation.
  3. Update child payloads from entity_type/entity_id to disbursement_id.

Version 3.5.0

Release Date: Breaking Changes

Major restructuring release. loan and purchase categories are replaced by disbursement with kind, payment gains status/payment_fee/installment_number, and schedule is modeled as expected payments.

Disbursement Category

  • Unified entity replaces loan and purchase.
  • kind: "loan" requires disbursement_date and maturity_date.
  • kind: "purchase" requires operation_date, payment_date, estimated_delivery_date, goods_description, and goods_status.

Payment Category

  • Added required status: expected, paid, or canceled.
  • Added optional payment_fee and installment_number.
  • Use expected payment series to model schedules.

Migration Notes

  1. Replace loan payloads with disbursement + kind: "loan".
  2. Replace purchase payloads with disbursement + kind: "purchase".
  3. Replace schedule payloads with payment events using status: "expected".

Version 3.4.3 Deprecated

Release Date:

Documentation-only update clarifying the installments array structure for Schedule events.

Schedule Documentation

  • Documented required installments keys and optional principal_amount.
  • Added detailed installments JSON example.

Version 3.4.2 Deprecated

Release Date: Important Change

Insurance events gained required percentage field for coverage amount between 0 and 1.

Insurance Events

  • Added percentage as required field.
  • Default value is 1 for full coverage.

Migration Notes

  1. When creating or updating Insurance events, include percentage.
  2. Use 1 for full coverage or a decimal such as 0.75 for partial coverage.

Version 3.4.1 Deprecated

Release Date:

Aligned required field validations, examples, FAQs, and validator behavior with official required fields specification.

Required Fields Updates

  • Added step as required for Loan, Restructure, and Client events.
  • Corrected required/optional fields across Purchase, Credit Decision, Asset, Goods Status, and Client events.
  • Updated validators with corrected required fields.

Migration Notes

  1. Include newly required fields when sending legacy categories from this version.

Version 3.4.0 Deprecated

Release Date:

Initial API specification with event ingestion, idempotency, API key authentication, rate limiting, JSON validator, and FAQ documentation.

Features

  • Event ingestion with idempotency guarantees.
  • API key authentication.
  • Support for initial event categories and JSON payload validator.

Search documentation

Start typing to find your next step.

    navigate Enter openLocal, private search