Skip to content
Documentation
English
Open the app

Errors and limits

Error format of the NovaFisko API, HTTP codes, machine codes, rate limits per route and what to do after a 429.

The API uses standard HTTP codes and always returns a JSON body when an error occurs.

Error format

{
  "message": "Le plafond de 5 dossiers de votre licence est atteint.",
  "code": "company_limit_reached",
  "errors": {
    "name": ["Le plafond de 5 dossiers de votre licence est atteint."]
  }
}
Field Presence Description
message always Readable text, translated into the user's language
code when it is a business error Stable identifier, in English, to use in your code
errors validation errors Object field → list of messages. Nested fields use dot notation: lines.0.account_id

Some errors add fields of their own, for example required_ability, server_version and server_data, or blocker_codes for Peppol.

Tip

Handle the HTTP status first, then code if present. Never parse the text of message.

HTTP codes

Status Meaning What to do
200 Success
201 Resource created
202 Request accepted, processed immediately or later (webhook test)
204 Success with no body (deletion, logout)
401 Token absent, invalid, revoked or expired Log in again or use another token
403 Action forbidden for this token or this role Read code
404 Unknown resource, or company the user has no access to Check the public_token and the rights
409 Version conflict (X-Base-Version) Reload, merge, resend
422 Data refused (validation or accounting rule) Fix according to errors and code
429 Too many requests Wait Retry-After seconds
500 Internal error Retry later with the same idempotency key
503 Service unavailable or under maintenance, always with a code Retry with an increasing delay

A company that exists but that you have no access to responds 404 and not 403. This is intentional: the API does not reveal the existence of a company to anyone who is not authorised to see it.

Machine codes

Authentication and access

code Status Meaning
token_ability_missing 403 The integration token lacks the ability given in required_ability
session_token_required 403 Route reserved for session tokens
token_limit_reached 422 At most 50 integration tokens per user
webhook_limit_reached 422 At most 20 endpoints per firm
webhook_url_refused 422 Webhook URL refused (HTTPS required, private or unresolved host)
demo_mode 403 External effect blocked in the demo firm
demo_unavailable 503 The demo firm is not available
service_unavailable 503 General unavailability

Licence and caps

code Status Meaning
company_limit_reached 422 Company cap of the licence or plan reached
user_limit_reached 422 User cap of the licence reached
invalid_key 422 Unknown activation key
expired_key 422 Expired activation key
exhausted_key 422 Key already used the allowed number of times
key_reserved 422 Key reserved for another firm
already_active 422 This key is already active on the firm

Concurrency and history

code Status Meaning
conflict 409 or 422 The record has changed on the server since you read it
period_locked 422 Locked period or closed fiscal year
vat_locked 422 VAT return validated or filed
entry_immutable 422 A posted entry cannot be modified: it must be reversed
already_reverted, already_reversed 422 The revert or the reversal has already taken place
in_use, booked 422 Deletion refused: the record is in use or booked
code_taken 422 Restore refused: the code has been reused in the meantime
not_revertible, nothing_to_revert, operation_not_found 422 or 404 Revert impossible
force_forbidden, read_only 403 Insufficient role

Synchronisation

These codes appear in the result of each sync/push mutation, not as an HTTP status.

code Meaning
locked Accounting data posted or locked on the server side
validation Data refused, details in errors
not_supported_offline This operation does not go through sync/push
not_found, forbidden, invalid_mutation, unknown_temp_id, unprocessable Mutation refused permanently
server_error Temporary error, retryable: true
conflict_already_resolved, conflict_choice_unavailable Conflict resolution refused (422)

Peppol

Peppol refusals return blocker_codes, a list among: provider_not_configured, vat_number_missing, address_missing, email_missing, already_registered, managed_by_novadesko, registered_elsewhere, not_registered, provider_error.

{
  "message": "Le dossier ne peut pas être inscrit sur Peppol.",
  "errors": {"peppol": ["Le dossier ne peut pas être inscrit sur Peppol."]},
  "blockers": ["L'adresse e-mail de contact est manquante."],
  "blocker_codes": ["email_missing"],
  "state": {"status": "none", "can_register": false}
}
Note

Not all 422 errors carry a code. A simple validation error only contains message and errors. Allow for this case.

Validation error

{
  "message": "Le champ entry date est obligatoire. (et 1 erreur supplémentaire)",
  "errors": {
    "entry_date": ["Le champ entry date est obligatoire."],
    "lines.1.account_id": ["Le champ lines.1.account_id est obligatoire quand lines.1.account n'est pas présent."]
  }
}

Rate limits

Limits apply per minute. They are counted per user for authenticated routes and per IP address for public routes. Routes that do not appear in this table have no limit of their own.

Route Limit per minute
POST auth/login 20
POST auth/demo 10
POST auth/tokens 20
POST firms/{firm}/webhooks/{id}/test 12
GET pricing, GET pricing/simulate 120
POST firms/{firm}/licence/activate 10
POST firms/{firm}/sync 6
GET lookup/search, lookup/companies/{identifier}, lookup/vat 60
GET lookup/cities, lookup/streets 120
POST companies/{company}/apply-sheet, third-parties/{id}/apply-sheet 30
POST document-imports 30
POST document-imports/{id}/process, .../validate 120
POST document-imports/{id}/retry 60
POST documents/book-all 5
POST bank-transactions/match 10
POST bank-transactions/book-matched 5
POST payments/sepa 20
GET vies, POST third-parties/{id}/vies 30
POST sync/novadesko 10
POST integrations/{id}/test, .../sync 12
POST peppol/register 6
POST peppol/refresh 12
GET peppol/lookup 30
GET third-parties/{id}/peppol 60
POST third-parties/peppol/check-all 6
GET exports/{type} 30
GET exports/{type}/preview-url 60
GET exports/full-dossier/{fiscalYear} 10
POST fiscal-years/{id}/year-end 5
GET history/export 30

Every response from a rate-limited route shows your consumption:

HTTP/1.1 200 OK
X-RateLimit-Limit: 30
X-RateLimit-Remaining: 27

429 response

HTTP/1.1 429 Too Many Requests
Retry-After: 41
X-RateLimit-Limit: 30
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1791188421
Content-Type: application/json

{"message": "Too Many Attempts."}

Wait the number of seconds given by Retry-After before retrying. X-RateLimit-Reset gives the reset time in Unix seconds.

import time
import requests

def call(method, url, **kwargs):
    for attempt in range(5):
        response = requests.request(method, url, timeout=60, **kwargs)
        if response.status_code == 429:
            # Honour the server delay, never hammer the API
            time.sleep(int(response.headers.get("Retry-After", "5")) + 1)
            continue
        if response.status_code in (500, 503):
            time.sleep(2 ** attempt)
            continue
        return response
    raise RuntimeError("NovaFisko API unavailable")
Warning

When you retry a write after a network error, a 500 or a 503, send the same X-Client-Mutation-Id again. Otherwise you risk creating a duplicate. See Idempotency.

Other limits

Limit Value
Files per upload in the importer 20
Size of an uploaded file 25 MB
Files extracted from a ZIP archive 50
Mutations per sync/push call 200
Changes per sync/pull call 500 by default, 1000 at most
Items per page 50 by default, 200 at most (500 for bank transactions)
Lifetime of a signed export preview URL 15 minutes

See also