Skip to content

Core concepts

Five conventions hold across every endpoint. Reading these first will save you the four integration bugs everyone hits.

Clients are addressed by registration number, never by our ID

Every client path is /clients/{countryCode}/{registrationNumber} — an ISO 3166-1 alpha-2 country code plus the company's national registration number.

You never have to store a Noja identifier. We return one (clientId) for logging and support, but no request requires it.

/clients/NL/68123456
/clients/NL/68123456/credit-conditions
/clients/NL/68123456/onboarding/steps/company

Invoices are addressed either by the invoiceId we return, or by your own invoiceNumber scoped to the client. Both work.

Registration number formats

The registration number is whatever the company's national business register issues. Two worked examples:

Country Identifier Format Example
NL KvK-nummer 8 digits, no dots or spaces 68123456
FI Y-tunnus 7 digits, hyphen, check digit 2597279-7

Countries we have not yet profiled accept letters, digits and hyphens up to 50 characters. A registration number that does not match a known format is rejected with validation_failed before anything is created.

Signing providers differ by country too, which is why onboarding submit returns a signing instruction rather than expecting you to know the provider. Read signing.method and follow it.

Noja will not accept invoices or company financials for a client without a record that the client agreed to share them. That record can arrive two ways:

  • You capture it and send the consent object when you create the client.
  • The client signs through Noja's own onboarding flow, in which case Noja records consent automatically — the agreement covers it.

Send the consent object when you have it. If you do not, create the client anyway and hand the user to the hosted onboarding flow; consent is recorded the moment they sign.

Until one of those happens, POST /invoices returns 403 consent_required. Client creation and onboarding are never blocked — only financial data is.

Money is always in minor units

Every amount is an integer of cents plus a currency, never a decimal.

{ "amount": 424250, "currency": "EUR" }   // €4,242.50

This is deliberate: it removes float rounding from financing maths entirely. Sending 4242.50 is rejected with validation_failed.

The most common integration bug

Sending euros where cents are expected under-reports an invoice by a factor of 100, and the invoice will usually still be accepted — it just scores as trivially small. Multiply by 100 at the boundary and assert it once in your tests.

Every write takes an idempotency key

Send Idempotency-Key on every POST and PATCH. Replaying a key returns the original response unchanged rather than creating a duplicate — safe for retries, queue redelivery, and nervous cron jobs.

-H "Idempotency-Key: inv-68123456-2026-0417"

Keys live 24 hours. Use something derived from your own data rather than a random UUID, so a retry after a crash reuses the same key.

Dates are ISO 8601

Calendar dates are YYYY-MM-DD. Timestamps are RFC 3339 in UTC with a Z suffix.

Invoice dates are calendar dates — an invoice is issued on a day, not at an instant. Event timestamps are full timestamps.

{
  "issueDate": "2026-08-30",
  "occurredAt": "2026-09-14T10:02:47Z"
}

One error shape

Every failure returns the same envelope. fields is present only on validation errors.

{
  "error": {
    "code": "validation_failed",
    "message": "Check the highlighted fields",
    "fields": {
      "registrationNumber": "invalid",
      "amount.currency": "unsupported"
    }
  }
}

Branch on code, never on message — messages are written for humans and will change. The full code list is in Errors and limits.

Who writes what

This is the conceptual spine of the API, and it is worth internalising before you build.

Data Owner Partner can change it
Invoice amount, dates, counterparty Partner Yes, until the invoice is financed
Invoice payment outcome (history) Partner Yes, optionally
financingRequested flag Partner Yes, at any time while the invoice is open
Partner's own invoice status Partner Yes, free-text, never interpreted by Noja
Consent record Either You send it, or Noja records it at signing
Company name, contact, KYC data Partner Yes, through onboarding
Invoice state Noja No
Financing decision, approved amount, fee Noja No
Credit limit, pricing, APR Noja No
Client status Noja No

Attempting to write a Noja-owned field returns 422 validation_failed naming that field, rather than silently ignoring it.

Every endpoint page marks each operation as Partner writes or Noja writes so this stays visible as you read.