1. Before you start
API credentials
In Evoliz, go to Applications, then to the Available Connectors section, and enable the Evoliz API connector (the API tag helps you find it). You can then generate a set of credentials: a public key and a secret key.
The secret key must be stored server-side only. It grants full access to your account data for as long as it is valid.
Your customer number
Every company endpoint is scoped by your customer number (companyid). To find it, log in to
Evoliz and look just below your first and last name, in the bottom-left corner: the first part is
your customer number.
Base URL and authentication
The base URL is https://www.evoliz.io/.
Authenticate by posting your keys to /api/v1/login:
POST /api/v1/login HTTP/1.1
Host: www.evoliz.io
Content-Type: application/json
{
"public_key": "YOUR_PUBLIC_KEY",
"secret_key": "YOUR_SECRET_KEY"
}
{
"access_token": "eyJ0eXAiOiJKV1Qi...",
"expires_at": "2026-09-11 14:35:12",
"scopes": ["company_users", "client", "sale_order", "sale_invoice", "..."]
}
Pass the token on every subsequent call:
Authorization: Bearer YOUR_ACCESS_TOKEN
The token is valid for 20 minutes, as stated by expires_at. Cache it server-side and reuse it
until it expires, then log in again — do not authenticate on every call, you will exhaust your rate
limit. Never store the token anywhere a browser can read it: it is a server-side secret with the
same reach as your keys.
The companies/{companyid} path segment is optional for a company user, so
/api/v1/companies/{companyid}/clients and /api/v1/clients are equivalent. The explicit form is
used throughout this guide.
Permissions
The scopes returned at login mirror the permissions of the Evoliz user owning the credentials.
An e-commerce integration needs:
| Area | Scope |
|---|---|
| Customers | client |
| Customer contacts | client_contact_client |
| Catalog | article_article |
| Orders | sale_order |
| Invoices | sale_invoice |
| Payments | sale_payment |
| Credit notes | sale_credit |
A 403 on an endpoint means the scope is missing: grant the corresponding access to that user in
Evoliz, then log in again to obtain a token carrying it. Transforming an order into an invoice
requires both sale_order and sale_invoice.
Rate limits
The API allows 100 requests per minute per IP by default. Every response carries:
x-ratelimit-limit— the ceiling for the route;x-ratelimit-remaining— how many requests are left in the current minute.
Exceeding it returns 429 Too Many Requests. Since the naive "search then create" loop costs two
calls per entity, a single order can easily cost eight or more; a catch-up batch of a few hundred
orders will hit the limit. So:
- read
x-ratelimit-remainingand back off before you are throttled, and retry429with an exponential delay; - cache reference data and the identifiers you have already resolved instead of re-reading them for every order;
- spread catch-up batches over time rather than firing them in parallel.
There is no webhook: Evoliz never calls your store. Anything you need to observe (a payment recorded in Evoliz, a transmission status) has to be polled, which is a further reason to keep your request budget under control.
2. Reference data
Four lists describe how your account is configured. They change rarely: read them once at startup and cache them, do not fetch them per order.
| Endpoint | Use |
|---|---|
GET /api/v1/companies/{companyid}/vat-rates |
VAT rates you are allowed to send on a line |
GET /api/v1/companies/{companyid}/paytypes |
Payment methods and their paytypeid |
GET /api/v1/companies/{companyid}/payterms |
Payment terms and their paytermid |
GET /api/v1/companies/{companyid}/unit-codes |
Unit codes accepted on articles |
Do not hardcode these values. The identifiers are stable, but which ones your account actually offers is not.
List endpoints are paginated (data, links, meta), 15 results per page by default and up to
100 with per_page.
3. Prices, VAT and discounts
Sending prices inclusive or exclusive of VAT
Most storefronts hold B2C prices inclusive of VAT. Declare that at document level and send your prices as they are:
{
"prices_include_vat": true,
"items": [
{ "designation": "Banana mug", "quantity": 2, "unit_price": 14.90, "vat_rate": 20 }
]
}
unit_price is interpreted according to prices_include_vat — inclusive of VAT when true,
exclusive when false or omitted. It accepts up to 4 decimals and cannot be negative.
The older
unit_price_vat_excludefield is deprecated. Useunit_priceonly; sending both in the same request is rejected.
VAT rates
vat_rate must be one of the rates configured on your account — read them from
GET /vat-rates. Any other value returns 400, and the error message lists the accepted rates.
For a line that carries no VAT, omit vat_rate entirely. 0 is not a configurable rate and
will be rejected. (A VAT-exempt company carries a document-level exoneration reason instead.)
Shipping costs
Evoliz has no dedicated shipping field. Add shipping as a regular line, with its own VAT rate when it is taxed:
{ "designation": "Frais de livraison", "quantity": 1, "unit_price": 4.90, "vat_rate": 20 }
Discounts
Per-line discount — use rebate on the line. It takes an amount or a percentage string:
{ "designation": "Banana mug", "quantity": 2, "unit_price": 14.90, "vat_rate": 20, "rebate": "10%" }
Order-level discount — use global_rebate on the document, again as an amount or a percentage:
{ "global_rebate": "5%" }
Do not model a discount as a line with a negative
unit_price. Negative unit prices are rejected on all sale documents. If your store computes its global discount differently from Evoliz (typically because it applies it on VAT-inclusive totals), sendglobal_rebateas a plain amount rather than a percentage so both systems agree on the figure.
4. Never create a duplicate
Your store is the source of truth; Evoliz must converge towards it. Before creating anything, look for it, and use a key that matches exactly rather than a fuzzy one.
| Entity | Key to use | Filter | Match |
|---|---|---|---|
| Article | reference |
?reference=SKU-42 |
exact |
| Client | code |
?code=CUST-1234 |
exact |
| Contact | email + clientid |
?clientid=1&email=... |
exact |
| Order | external_document_number |
?search=... |
fuzzy |
The best approach is to write your own store identifiers into these fields when you create the
record, so that a later lookup is a single exact query: your SKU into the article reference, your
customer id into the client code, your order id into the order external_document_number.
Two things to be careful about:
search is not an exact filter. There is no exact filter on external_document_number. The
search parameter performs a partial match across several columns at once — document number,
external document number, amounts, description and client name. So ?search=1042 can return an
order whose amount is 1042, or whose client is named "1042". Always re-check the candidates
client-side:
GET /api/v1/companies/{companyid}/sale-orders?search=WEB-1042&period=currentyear
then keep only the result whose external_document_number is exactly WEB-1042.
List endpoints on sale documents apply a default period. GET /sale-orders, GET /invoices
and GET /credits are filtered by your account's default period when no period is passed — so an
order placed outside that window is invisible, and you would create it a second time. Always pass
an explicit period when looking for an existing document:
GET /api/v1/companies/{companyid}/sale-orders?search=WEB-1042&period=custom&date_min=2026-01-01&date_max=2026-12-31
Clients, contacts and articles are not affected: they have no period filter.
5. Catalog
Look the article up by its reference, which is unique per company:
GET /api/v1/companies/{companyid}/articles?reference=SKU-42
If the list is empty, create it:
POST /api/v1/companies/{companyid}/articles
Content-Type: application/json
{
"reference": "SKU-42",
"designation": "Banana mug",
"unit_price": 14.90,
"vat_rate": 20,
"ttc": true,
"nature": "product"
}
reference and designation are required. Note that reference is limited to 25 characters:
if your SKUs are longer, derive a stable shortened key rather than truncating blindly, or you will
collide two products onto one article. unit_price is optional — you may keep prices in your store
only and send them on each order line.
Selling a catalog article is not mandatory. An order line can carry an articleid, or describe a
free-form line with designation, quantity and unit_price. Reference the article when you want
the sale to feed Evoliz's catalog statistics; use free-form lines for one-offs like shipping.
6. Customers
Looking up and creating
Look the customer up by the code you assigned:
GET /api/v1/companies/{companyid}/clients?code=CUST-1234
If it does not exist, create it:
POST /api/v1/companies/{companyid}/clients
Content-Type: application/json
{
"code": "CUST-1234",
"name": "Doe",
"type": "Particulier",
"address": {
"addr": "12 rue des Lilas",
"postcode": "83130",
"town": "La Garde",
"iso2": "FR"
}
}
name, type and address are required. Inside the address, postcode, town and iso2 are
required, and addr — the first address line — is required too.
Note the field names: the address city is town, and the country is iso2 on write. When
you read a client back, the country comes as an object: address.country.iso2.
code is optional as far as the API is concerned, but it is what makes your integration
idempotent, so always send it. It is limited to 20 characters and must be unique within your
company.
Client types
type takes one of three labels:
| Value | Meaning |
|---|---|
Particulier |
Private individual — the usual B2C case |
Professionnel |
Business |
Administration publique |
Public administration |
The type is not cosmetic: it drives which identifiers are mandatory, and how the invoice must later be transmitted. Map it from what your store knows about the buyer — typically, whether a company name or VAT number was supplied at checkout.
Identification requirements
For a private individual, the payload above is complete.
For a French business or public administration, three identifiers are required:
{
"code": "CUST-1235",
"name": "Ministère de l'Économie",
"type": "Professionnel",
"business_number": "13002526500013",
"business_identification_number": "130025265",
"vat_number": "FR00300256549",
"address": {
"addr": "139 rue de Bercy",
"postcode": "75012",
"town": "Paris",
"iso2": "FR"
}
}
| Field | Content | Format |
|---|---|---|
business_number |
SIRET | exactly 14 digits |
business_identification_number |
SIREN | exactly 9 digits |
vat_number |
Intra-community VAT number | format-checked |
Collect these at checkout for your B2B customers. A business order cannot be invoiced without them, and they cannot be guessed afterwards.
For a non-French customer, only vat_number is relevant, and it is optional.
7. Customer contacts
Contacts hold the person to reach for a given client — useful to send the invoice by email. Look one up by client and email:
GET /api/v1/companies/{companyid}/contacts-clients?clientid=1&email=john@example.com
Then create it if needed:
POST /api/v1/companies/{companyid}/contacts-clients
Content-Type: application/json
{
"clientid": 1,
"civility": "M.",
"lastname": "Doe",
"firstname": "John",
"email": "john@example.com",
"favorite": true,
"label_tel_primary": "6",
"tel_primary": "0612345678"
}
clientid and lastname are required; email becomes required when favorite is true.
label_tel_primary is required whenever you send tel_primary, and takes a numeric label id from
the account's list (6 is "Portable").
The API does not enforce uniqueness on a contact's email, so the lookup above is your only protection against creating the same person twice.
8. Creating the order
POST /api/v1/companies/{companyid}/sale-orders
Content-Type: application/json
{
"external_document_number": "WEB-1042",
"documentdate": "2026-09-11",
"clientid": 1,
"business_process": "goods",
"prices_include_vat": true,
"term": {
"paytermid": 17
},
"items": [
{ "articleid": 12, "quantity": 2 },
{ "designation": "Banana mug", "quantity": 1, "unit_price": 14.90, "vat_rate": 20 },
{ "designation": "Frais de livraison", "quantity": 1, "unit_price": 4.90, "vat_rate": 20 }
]
}
Required: documentdate, clientid, term.paytermid and a non-empty items array.
external_document_number carries your own order reference. It is unique per company, limited
to 40 characters, and is what you match on to avoid creating the order twice.
documentdate uses Y-m-d and cannot be more than 30 days in the future.
term.paytermid is the payment term. For a store that takes payment at checkout, 17
("A la commande") is the one that reflects reality; 1 ("A réception") means payment on receipt.
Read GET /payterms for the full list. Two terms need a companion field: 18 requires
term.duedate, and 16 requires term.paydelay and term.endmonth.
Lines either reference a catalog article through articleid — designation, price and VAT are
then taken from the article — or describe themselves with designation, quantity and
unit_price. An optional type lets you insert presentation lines: text for a comment,
sub_total for a subtotal. Those carry no price, and rebate only applies to regular
(article) lines.
Business process
business_process states the nature of what you sell:
| Value | Meaning |
|---|---|
goods |
Goods |
services |
Services |
mixed |
Both |
It is the single most important field to get right, because an order without a business process cannot be invoiced: the transformation in the next section will fail, and the field cannot be inferred after the fact. Set it on every order you create.
For a store shipping physical products, that is goods. mixed is rejected when the client is a
Particulier, so for a B2C order carrying both goods and services, pick the one that dominates.
After creating the order
The order is created in draft and numbered immediately. Store the returned orderid alongside your
own order, together with the clientid and the article ids you resolved: it saves you the lookups
on the next run.
Depending on your account settings, an analytic code (analyticid) or a sale classification
(items[].sale_classificationid) may be mandatory. If a create call returns 400 naming one of
them, that requirement comes from your Evoliz configuration.
9. Invoicing the order
Invoice the order when you have shipped it, or when payment is captured — whichever matches your accounting practice.
POST /api/v1/companies/{companyid}/sale-orders/{orderid}/invoice
This returns a draft invoice, carrying the order's lines, client and business process. Finalize it to get a definitive invoice number:
POST /api/v1/companies/{companyid}/invoices/{invoiceid}/create
Content-Type: application/json
{
"auto_recovery_enabled": false
}
auto_recovery_enabled is optional; omitted, your account's default applies. Leave automatic
dunning off for orders already paid at checkout.
Both steps are one-way. A finalized invoice cannot be edited or deleted — it can only be corrected by a credit note. So check the draft before finalizing it if your order data may still change.
Requires the sale_invoice scope in addition to sale_order.
10. Transmitting the invoice
A finalized invoice destined to a business or a public administration must reach the buyer through a Plateforme Agréée (PA). This is a three-step flow, and it is skipped entirely for private individuals.
Step 1 — find the buyer's electronic address
GET /api/v1/companies/{companyid}/invoices/{invoiceid}/electronic-addresses
The lookup uses the buyer's SIREN as recorded when the invoice was created, so you pass nothing.
Two shapes of answer, both 200:
{ "skip": true }
The buyer is out of scope — a private individual, a non-French customer, or a client with no SIREN. Nothing more to do: stop here. This is the normal answer for B2C orders.
{
"total": 1,
"results": [
{
"addressing_identifier": "0225:130025265_13002526500013_SERVICE-JURIDIQUE",
"siren": "130025265",
"siret": "13002526500013",
"status": "Enabled",
"business_name": "MINISTERE DE L ECONOMIE ET DES FINANCES",
"routing_identifier": "SERVICE-JURIDIQUE",
"needs_legal_commitment": false
}
]
}
Pick a line whose status is Enabled — only those can currently receive an invoice. When several
are returned, the choice belongs to the buyer, so record it against the customer rather than asking
again on every order.
A 422 with invalid_client_siren means the client's SIREN is malformed: fix the customer record,
then invoice again.
Step 2 — set the routing address
PATCH /api/v1/companies/{companyid}/invoices/{invoiceid}/routing-address
Content-Type: application/json
{
"addressing_identifier": "0225:130025265_13002526500013_SERVICE-JURIDIQUE"
}
addressing_identifier alone identifies the recipient; everything else is re-derived and
re-validated server-side. When the chosen address has needs_legal_commitment set, also send
legal_commitment_code — the buyer's commitment ("engagement") number, up to 50 characters.
Omitting it there returns 422 with legal_commitment_required.
This writes the address onto the invoice's Factur-X. It does not send anything yet.
Step 3 — transmit
POST /api/v1/companies/{companyid}/invoices/{invoiceid}/transmit
Evoliz regenerates the Factur-X, submits it to the PA and returns the PA's response:
{
"success": true,
"flow_id": "b1c2d3e4-...",
"tracking_id": "evoliz-inv-24459-2026-0001",
"tech_status": "Pending"
}
Persist tracking_id against your order: it is your handle on this invoice for any later
reconciliation.
The invoice must be finalized before you transmit it. Transmission is guarded against duplicates,
and returns 409 with:
already_emitted— the invoice has already been transmitted. Treat it as success; do not retry.retry_too_soon— a previous attempt failed and the retry delay has not elapsed. Retry later.
A 503 means the PA or the dependency is unreachable. The invoice is unchanged, so retry with a
backoff.
11. Recording the payment
Record the payment when you actually receive it:
POST /api/v1/companies/{companyid}/invoices/{invoiceid}/payments
Content-Type: application/json
{
"paydate": "2026-09-11",
"label": "Stripe ch_3PqR2s...",
"paytypeid": 3,
"amount": 34.70
}
paydate, label and paytypeid are required, and so is amount for every payment method but
one. label is capped at 80 characters — a good place for your payment-processor reference, which
makes reconciliation possible later.
amount cannot exceed what remains to be paid on the invoice; the error message tells you the
actual remainder. Partial payments are allowed: post several payments to settle an invoice
progressively.
paytypeid identifies the payment method — see the appendix,
and read GET /paytypes for what your account offers. One value behaves differently: 13 (Avoir) settles the invoice with an
existing credit note. It requires creditid and prohibits amount, which is derived from the
credit note. The credit note must belong to the same client and not exceed the remaining balance.
Requires the sale_payment scope.
12. Refunds
A finalized invoice is never modified; it is corrected by a credit note.
There are two ways to create one, and which applies depends on whether the invoice has been paid:
The invoice has no payment recorded — derive the credit note from it:
POST /api/v1/companies/{companyid}/invoices/{invoiceid}/credit
for a full credit, or POST /api/v1/companies/{companyid}/invoices/{invoiceid}/partial-credit with
the lines to credit, for a partial one. The client, lines and business process are inherited. On a
partial credit, prices_include_vat must match the invoice's own mode.
The invoice has been paid — which is the normal case for a store that settles at checkout — those two endpoints are rejected: a credited invoice must not already carry payments. Create a standalone credit note instead:
POST /api/v1/companies/{companyid}/credits
Content-Type: application/json
{
"documentdate": "2026-09-20",
"clientid": 1,
"business_process": "goods",
"prices_include_vat": true,
"invoice_ref": "FA2026-0042",
"invoice_ref_date": "2026-09-11",
"term": { "paytermid": 17 },
"items": [
{ "designation": "Banana mug", "quantity": 1, "unit_price": 14.90, "vat_rate": 20 }
]
}
invoice_ref and invoice_ref_date are required, and carry the corrected invoice's number and
date — this is what ties the credit note to the original. invoice_ref is limited to 35 characters
and accepts only letters, digits, -, + and _. business_process is required as well.
Then finalize it, exactly as for an invoice:
POST /api/v1/companies/{companyid}/credits/{creditid}/create
Credit notes go through the same transmission flow as invoices — electronic-addresses,
routing-address, transmit — under /credits/{creditid}/. For a B2C refund, step 1 answers
skip: true and you are done.
Requires the sale_credit scope.
13. Handling errors
| Code | Meaning | What to do |
|---|---|---|
400 |
Validation failed | Fix the payload. Never retry as-is. |
401 |
Token missing or expired | Log in again, replay the call. |
403 |
Missing scope, or client/document not accessible | Grant the access in Evoliz; do not retry blindly. |
404 |
Unknown resource | Your stored id is stale; look the resource up again. |
409 |
Already transmitted, or retried too soon | See transmitting the invoice. |
422 |
Business rule rejected the request | Read the error code; usually the customer data needs fixing. |
429 |
Rate limit exceeded | Back off and retry. |
503 |
Dependency unreachable | Retry with a backoff; nothing was changed. |
Validation errors return 400 — not 422 — with the offending fields:
{
"error": "Bad Request Error",
"message": {
"items.0.vat_rate": ["The items.0.vat_rate must be one of the VAT rates configured for the company: 20, 10, 5.5."]
}
}
Two habits make a store integration resilient:
Persist the Evoliz identifiers as you obtain them. clientid, articleid, orderid,
invoiceid, creditid, tracking_id — stored against your own records, they turn every later
call into a direct one and remove most of your rate-limit pressure.
Make each step resumable. Creating the order, invoicing it, finalizing, transmitting and paying are five separate calls, and a run can die between any two of them. Record which step succeeded so a retry picks up where it stopped instead of starting over — starting over is what creates duplicate orders.
Appendix: payment methods
Identifiers are stable, but only those enabled on your account are usable. Read
GET /api/v1/companies/{companyid}/paytypes rather than hardcoding this table.
paytypeid |
Payment method |
|---|---|
| 1 | PayPal |
| 2 | Virement (wire transfer) |
| 3 | Carte bancaire (credit card) |
| 4 | Chèque (cheque) |
| 5 | Espèces (cash) |
| 6 | Autres (other) |
| 7 | Chèque Emploi Service Universel (CESU) |
| 8 | Prélèvement (direct debit) |
| 9 | Lettre de change (bill of exchange) |
| 10 | Traite (draft) |
| 11 | Chèque vacances (holiday voucher) |
| 12 | Contre-remboursement (cash on delivery) |
| 13 | Avoir (credit note) — settles with an existing credit note, see recording the payment |