Authenticating with the 4invoices API

Exchange client_id and client_secret for a one-hour Bearer token, server-to-server only.

Exchange client_id and client_secret for a one-hour Bearer token, server-to-server only.

← Back to the API documentation

Your first invoice in 6 steps

  1. Mint the credential in the panel under Invoicing → Settings → API access and copy the client_secret immediately — it is shown once.
  2. Send POST /v1/auth/token with grant_type client_credentials, client_id and client_secret.
  3. You get back access_token, token_type Bearer, expires_in 3600 and the list of granted scopes.
  4. Put the token in an Authorization: Bearer <access_token> header on every other call.
  5. Check what you actually got with GET /v1/me: tenant, scopes and the current rate buckets.

Every endpoint

MethodEndpointScopeIdempotency-KeyRequests / minute
POST/auth/token
Exchange credentials for an access token
——120
GET/me
Tenant, scopes and current limits
invoices:read—120

What the 4invoices API is

bash
curl -sS -X POST https://api.4invoices.net/v1/auth/token \
  -H 'Content-Type: application/json' \
  -d '{
    "grant_type": "client_credentials",
    "client_id": "cli_XXXXXXXXXXXX",
    "client_secret": "sec_XXXXXXXXXXXXXXXXXXXX"
  }'

# → {"access_token":"eyJ...","token_type":"Bearer","expires_in":3600,
#    "scope":"invoices:read invoices:write efactura:submit"}
bash
curl -sS https://api.4invoices.net/v1/me \
  -H "Authorization: Bearer $ACCESS_TOKEN"

# → tenant, granted scopes and the current rate-limit buckets
javascript
// Cache the token; do not mint one per request.
let cached = { token: "", expiresAt: 0 };

async function getToken() {
  if (cached.token && Date.now() < cached.expiresAt - 60_000) return cached.token;

  const res = await fetch("https://api.4invoices.net/v1/auth/token", {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({
      grant_type: "client_credentials",
      client_id: process.env.FOURINVOICES_CLIENT_ID,
      client_secret: process.env.FOURINVOICES_CLIENT_SECRET,
    }),
  });
  if (!res.ok) throw new Error(`token ${res.status}`);

  const data = await res.json();
  cached = { token: data.access_token, expiresAt: Date.now() + data.expires_in * 1000 };
  return cached.token;
}

https://api.4invoices.net/v1 · https://api.4invoices.net/v1/openapi.json

Scopes

  • invoices:read
  • invoices:write
  • clients:read
  • clients:write
  • efactura:read
  • efactura:submit
Server-to-server only. Never call the API from browser JavaScript: the client_secret would reach the visitor, and CORS blocks every origin anyway.

Errors

StatusTypeCode
401authentication_errorinvalid_client
401authentication_errortoken_expired
401authentication_errorinvalid_token
403permission_errorinsufficient_scope

Common mistakes

  • The token endpoint has its own per-IP limit. One token per process, refreshed before expiry — not one per request.
  • The client_secret is stored only as a hash; lose it and you mint a new credential and revoke the old one.
  • Issue separate credentials for the ERP, the shop and internal scripts, each with minimal scopes. Revoking one does not affect the others.
  • CORS allows no browser origin: the API is server-to-server by design.

Developer questions and answers

What happens if the client_secret is wrong?

You get 401 with code invalid_client. A too-short secret fails earlier, at validation, with 422 validation_failed and param client_secret.

Is there a separate sandbox?

No. There is one production host. Test with proformas (document_kind proforma) and with /invoices/preview-totals, which consumes no fiscal number.

Can I narrow the scopes of an existing credential?

Yes, from the panel. Tokens already issued keep the scopes they were minted with until they expire, within one hour.