Versioning
Flat-versioned /v1 policy with Factuarea-Version header. Stability and deprecation commitments.
The Factuarea API follows a flat-versioned URL policy (/v1) combined
with an optional date header for non-breaking evolution. The commitment is
clear: once published, /v1 stays stable. Incompatible changes require
/v2.
URL version
https://api.factuarea.com/v1/...v1 is our first public version (May 2026). No prior versions are
accessible.
When /v2 is designed:
/v1and/v2coexist for at least 12 months./v1routes do not change in that window (no payloads, status codes, fields or semantics).- Email notices to developers with active keys, banner in the docs, headers on responses (see below).
Factuarea-Version header
Factuarea-Version: 2026-06-01Date-versioning works the Stripe way. There is a registry of supported
versions (YYYY-MM-DD dates): 2026-06-01, 2026-09-01 and
2026-10-01. The default is 2026-06-01, which is not the latest. The
version that applies to a request — the effective version — is resolved
in this order:
- The
Factuarea-Versionrequest header, if you send one. - Otherwise, the version pinned on your API key (set when the key is created; null means no pin).
- Otherwise, the default version,
2026-06-01.
The effective version is echoed on every response in the
Factuarea-Version header, so you always know which calendar version
served your request.
Factuarea-Version: 2026-06-01Pinning a version (by header or on the key) freezes the behavior of the
subset of endpoints that receive non-breaking incremental improvements
(new response fields, new optional parameters). Without a header or a
pin, you get the default version, 2026-06-01. Each newer date is opt-in
and includes the changes of the dates before it.
Opting into 2026-09-01 changes two things, and nothing else:
Change in 2026-09-01 | What you get on 2026-06-01 |
|---|---|
Bulk-delete responses use the transversal partial-success shape {total, successful, failed, failures[{id, error_code, error_message}]}. | The previous shape {object: "bulk_delete_result", deleted, failed[{id, reason}]}. |
The payment-gate errors that existed before the category carry error.type: "payment_required_error": payment_method_required, employee_seat_payment_method_required and employee_seat_charge_failed. seat_charge_failed and gestoria_plan_required were reclassified the same way; the single breaking window retired them and replaced them with the additional tax ID codes additional_nif_charge_failed and additional_nif_plan_required, which carry payment_required_error on every version, so they have no earlier type to fall back to. | error.type: "invalid_request_error" for the three that still exist, exactly as before. |
The error reclassification does not touch error.code,
error.subcode or the HTTP status — they are 402 with the same code on
every version. If you branch on code (which is what we recommend),
nothing changes for you either way. addon_required and the additional
tax ID codes are newer and always carry payment_required_error.
Invoice vocabulary in 2026-10-01
2026-10-01 separates issuing an invoice from delivering it to the
customer. It changes the invoice contract, and nothing else:
Change in 2026-10-01 | What you get on 2026-06-01 and 2026-09-01 |
|---|---|
An issued invoice has status: "issued". | status: "sent". |
issued_at holds the issuance instant; sent_at the first delivery to the customer, or null. | No issued_at; sent_at holds the issuance instant. |
is_sent and sent_via (email or manual) describe the delivery. | Not present. |
scheduled_action uses issue for "issue without sending". | draft, its previous name. |
GET /v1/companies/{company}/invoices/statuses lists issued, and by_status in GET /v1/companies/{company}/invoices/stats uses the key issued. | sent in the same position and as the key. |
POST /v1/companies/{company}/invoices/{invoice}/mark-sent records a manual delivery and rejects a draft with 422 invoice_cannot_be_marked_as_sent. | It issues a draft without emailing it. |
The version applies to every invoice object in any response, including
conversions, recurring invoice runs, GET /v1/companies/{company}/events and listed webhook
deliveries. Some parts are available on every version because they are
additive or input aliases: POST /v1/companies/{company}/invoices/{invoice}/issue, the is_sent
filter, generation_mode on recurring invoices, and status=sent,
new_status: "sent" and scheduled_action: "draft" as aliases of issued and
issue. See the release notes.
Webhook payload versions
Webhook deliveries follow the api_version of each endpoint, not the request
header. The supported payload versions are 2026-05-22, 2026-10-01 and 2026-10-05. The
endpoints that existed before that version were pinned to 2026-05-22 and
receive invoice snapshots in the previous vocabulary. An endpoint created
without api_version is pinned when it is created: with no header and no pinned
key, or from the app, it stays on 2026-05-22; if the request uses the REST
version 2026-10-01, it takes the latest payload version (2026-10-05). To receive the
new vocabulary, create it with Factuarea-Version: 2026-10-01 or with
"api_version": "2026-10-01". See
Webhooks.
Errors
The header is validated against the registry:
- Malformed value (not
YYYY-MM-DD, e.g.2026-05or15/05/2026) →400 parameter_invalid_formatwithparam: "Factuarea-Version". - Well-formed but unsupported (a valid date that is not in the
registry) →
400 unsupported_api_versionwithparam: "Factuarea-Version".
Omitting the header is never an error — it falls back to the key pin or the latest version.
What is breaking?
We consider breaking (forbidden in /v1):
- Renaming / removing JSON response fields.
- Changing a field's type (
string→int). - Changing the
type/codeof an existing error envelope. - Changing status codes (e.g. returning
201where it used to be200). - Making a previously optional request field required.
- Changing the format of an identifier (UUID v7 stays UUID v7).
- Removing an endpoint without a documented replacement and migration window.
- Changing the document state machine semantics.
We consider non-breaking (allowed without a new version):
- Adding new fields in responses.
- Adding optional parameters in requests.
- Adding new endpoints.
- Relaxing restrictions (raising a limit, accepting more formats).
- Adding new enum values to fields that aren't critical to client-side state machines.
- Improving error messages (changes
message, nottype/code). - Reclassifying the
typeof an existing error envelope behind a dated version: keys pinned to an earlier date keep receiving the previoustypebyte for byte, andcode/subcode/status never move. This is how the payment-gate errors were recategorised in2026-09-01. Doing it without a dated version is the breaking case listed above. - Renaming a value or changing the meaning of a field behind a dated
version, as
2026-10-01does with the invoice status: earlier dates keep the previous contract and input aliases keep existing requests working.
Deprecation policy
When an endpoint or field is marked deprecated within /v1 (e.g. a
legacy alias replaced by a canonical version):
- Email notice to developers with active keys affected.
- Banner on
docs.factuarea.comwith the changelog. - Headers on every response of the deprecated endpoint for at least
12 months before retirement (which only happens in
/v2):
Deprecation: true
Sunset: Wed, 15 May 2027 00:00:00 GMT
Link: <https://docs.factuarea.com/changelog#v1-deprecations>; rel="deprecation"
Link: <https://docs.factuarea.com/guides/migration-from-holded>; rel="alternate"- In
/v1the endpoint keeps working until the launch of/v2. The headers warn. - In
/v2the endpoint is retired / replaced. The window between the first warning and/v2is ≥ 12 months.
Migration between versions
Each migration (v1 → v2) ships with:
- A dedicated guide at
docs.factuarea.com/guides/migration-v1-v2. - Field-to-field and endpoint-to-endpoint mapping.
- Operational recommendations (keep both keys, dual-write during the transition).
- Webhooks: old events keep their shape; new events live in their own version declared in the payload.
Changelog
Every /v1 change (new field, deprecation, new event, validation fix)
is published in Changelog with tags:
feature— new field / endpoint / event.fix— bug fix.deprecation— field or endpoint marked obsolete (still active in/v1).breaking— only appears in/v2, never inside/v1.security— fix with security implications. Read it first.
Subscribe to the RSS feed at https://docs.factuarea.com/changelog.rss
or follow @factuarea on X for announcements.
Stability commitment
An integration built today against /v1 will keep working in /v1
for at least 24 months from today, without touching your code.
Support window for v1 → minimum 12 months after launch of v2.
That's the guarantee. Any exception will be communicated with generous timelines.