# Accounting platforms

> One API over Dinero and e-conomic — what is identical, what a platform can refuse, and the fields that name a choice out of its own lists.

Source: https://invoia.io/developers/api/platforms

An Invoia key is bound to one connected accounting integration, and that
integration is on one accounting platform — today **Dinero** or
**e-conomic**. Which one it is, is not something your integration has to
know.

Every endpoint under `/api/v1` behaves the same way on either platform:
the same request bodies, the same response shapes, the same status codes
and the same error `code` strings. You write the integration once, and it
keeps working when a customer of yours connects the other platform.

## What stays the same [#what-stays-the-same]

* **Field names never change by platform.** Notably
  `dinero_invoice_guid`, `dinero_invoice_number`,
  `dinero_credit_note_guid` and `dinero_credit_note_number&#x60; mean &#x2A;"the
  handle the accounting platform gave this document"*, whichever platform
  that is. The names are frozen `/v1` surface, so they stayed put when
  the second platform arrived rather than being duplicated per platform.
  On e-conomic they carry the booked document's own number.
* **`DINERO_API_ERROR` means the same thing.** It is the `502&#x60; for &#x2A;"the
  accounting platform behind your integration failed"*. Retry it with
  backoff on either platform.
* **`send_error` means the same thing.** `null` on a clean issuance, a
  string when the document was booked but the send to the customer
  failed. On e-conomic, sending is part of the booking call and cannot
  fail on its own, so a booked document there always reports `null` — a
  possible value on both, never a different shape.
* **Amounts, dates, currencies, recognition, accruals and month-end** are
  Invoia's own arithmetic and are identical by construction.

## Fields that name a platform's own choice [#fields-that-name-a-platforms-own-choice]

A few fields name an entry out of a list the *platform* keeps. A platform
that keeps no such list reads `null` and ignores the field, so it is
always safe to send one.

| Field                          | On Dinero                   | On e-conomic                                               |
| ------------------------------ | --------------------------- | ---------------------------------------------------------- |
| `customers.vat_zone_ref`       | `null` — no VAT zones       | **required** — the agreement's VAT zone                    |
| `customers.payment_terms_ref`  | `null`                      | the agreement's payment-terms entry                        |
| `customers.layout_ref`         | `null`                      | the customer's default document layout                     |
| `customers.customer_group_ref` | `null` — no customer groups | **required** — the customer group the customer is filed in |
| `products.account_ref`         | the account number, as text | **required** — the product group the revenue is filed in   |

Where a value is required and missing, the create comes back as `422
VALIDATION_FAILED` with the field named in `field_errors` — the same
shape as any other validation failure.

To discover the valid values, read an already-mirrored customer or
product (`GET /customers`, `GET /products`): every mirrored record echoes
the references the platform holds for it. The workspace's customer and
product editors list what the agreement offers.

## Capability-based refusals [#capability-based-refusals]

Where a platform genuinely cannot do something, the API says so in a
stable code rather than letting the platform's own error reach you. Two
codes carry this, and neither is retryable with the same payload:

| Code                      | Status | What happened                                                                                                                                                                                                                              |
| ------------------------- | ------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `PLATFORM_UNSUPPORTED`    | `403`  | The platform behind this key cannot do what the endpoint needs. The `message` names the platform and the capability.                                                                                                                       |
| `PLATFORM_NOT_CONFIGURED` | `409`  | The connection is missing a choice its platform requires of every record — an e-conomic agreement whose document defaults were never confirmed. An admin finishes the Configure step in the workspace; nothing about your request changes. |

```json
HTTP/1.1 403 Forbidden
{
  "error": {
    "type": "permission_error",
    "code": "PLATFORM_UNSUPPORTED",
    "message": "This integration is on a platform that does not support invoicing",
    "request_id": "req_…"
  }
}
```

Treat `PLATFORM_UNSUPPORTED` the way you treat `SCOPE_INSUFFICIENT`: a
property of the credential, not of the call. Stop issuing that request
for that key and surface the `message` to whoever connected the
integration.

Two further platform differences never reach the API as a refusal,
because nothing on the API asks for them:

* **e-conomic has no credit-note document type.** Crediting an invoice
  works on both platforms and `POST /credit-notes` is identical; what
  differs is the document that lands in the books — on e-conomic it is a
  customer invoice with negative amounts, drawn from the invoice number
  sequence.
* **e-conomic keeps no readable record of what became of a send**, and a
  booked document there cannot be sent again through the API. Invoia's
  own resend lives in the workspace, not on the Public API, so this shows
  up only there.

## E-invoices [#e-invoices]

Sending over the public-sector e-invoice network works on both platforms.
A customer whose `send_method` is `einvoice` on a platform that cannot
route one is refused **before anything is booked**, as `422
INVALID_SEND_METHOD` — the same code as an unknown send method, because
in both cases the fix is on the customer record.
