4invoices API за програмери

REST преку HTTPS, JSON, јавна OpenAPI спецификација. Фактурирање, клиенти, серии и целата национална постапка за е-фактурирање (ANAF e-Factura во Романија) — од вашиот ERP, продавница или CRM.

Јавниот API на 4invoices ги пренесува податоците за фактурирање од вашиот систем во 4invoices и ја однесува фактурата до крај, до националниот систем за е-фактурирање (во Романија: ANAF SPV). Една производствена адреса, еден client_credentials тек, идемпотентни фискални запишувања и една омотница за грешки на секоја рута.

https://api.4invoices.net/v1 · OpenAPI спецификација (не е потребен клуч)

Што е API-то на 4invoices

  • API-то на 4invoices е REST преку HTTPS, со JSON на влез и JSON на излез.
  • Една производствена адреса: https://api.4invoices.net/v1. Нема посебна sandbox адреса.
  • Спецификацијата OpenAPI 3.1 е јавна и без клуч — увезете ја директно во Postman или генератор на клиенти.
  • Секој одговор носи заглавие X-Request-Id; наведете го кога барате поддршка.
  • Секое фискално запишување е идемпотентно: повторување по истек на времето никогаш не издава втор документ.
Само сервер-кон-сервер. Никогаш не повикувајте го API од JavaScript во прелистувачот: client_secret би стигнал до посетителот, а CORS и онака блокира секое потекло.
  • Целата постапка на националното е-фактурирање преку API (во Романија: ANAF e-Factura): предпроверка, поднесување, следење, потпишан одговор и влезно сандаче. Ниеден румунски конкурент не ја нуди оваа можност.
  • SmartBill нема ниту една крајна точка за поднесување во националниот систем за е-фактурирање (ANAF, во Романија) — испраќањето е само поставка на сметката во корисничкиот интерфејс — и воопшто нема крајна точка за список на фактури.
  • Oblio може да создава клиенти и производи само како споредна последица на издавање документ, а не како основни ресурси.
  • Генерирање UBL XML без поднесување (CIUS-RO за Романија): го добивате законскиот документ како датотека, без да го допрете националниот систем.
  • Идемпотентност на фискалните запишувања: повторување по истек на времето не создава втора фактура и не троши втор број.
  • Програмски читливо влезно сандаче на фактури од добавувачи — основа за автоматско усогласување.

Првата фактура во 6 чекори

Пред првото барање
  1. Создајте акредитиви во панелот под Фактурирање → Поставки → API пристап и копирајте го client_secret — се прикажува еднаш.
  2. Комплетирајте го профилот на компанијата и активирајте барем една серија од видот fiscale, инаку издавањето враќа issuer_profile_incomplete или no_series_configured.
  1. Создајте акредитиви во панелот под Фактурирање → Поставки → API пристап и копирајте го client_secret — се прикажува еднаш.
  2. Комплетирајте го профилот на компанијата и активирајте барем една серија од видот fiscale, инаку издавањето враќа issuer_profile_incomplete или no_series_configured.
  3. Разменете ги акредитивите за токен: POST /v1/auth/token со grant_type client_credentials. Токенот важи еден час — чувајте го во кеш, не барајте нов за секое барање.
  4. Издајте ја фактурата: POST /v1/invoices со заглавие Idempotency-Key и vat_rate како процент (21, не 0,21).
  5. Извршете ја предпроверката (CIUS-RO за Романија) со POST /v1/invoices/{id}/efactura/validate, па испратете ја до националниот систем за е-фактурирање со POST /v1/invoices/{id}/efactura/submit.
  6. Следете го статусот со GET /v1/efactura/submissions/{id} додека не стигне до accepted или rejected.
bash
# 1 — токен (1 час, чувајте го во кеш)
ACCESS_TOKEN=$(curl -sS -X POST https://api.4invoices.net/v1/auth/token \
  -H 'Content-Type: application/json' \
  -d '{"grant_type":"client_credentials","client_id":"cli_XXX","client_secret":"sec_XXX"}' \
  | python3 -c 'import sys,json; print(json.load(sys.stdin)["access_token"])')

# 2 — издајте ја фактурата (Idempotency-Key е ЗАДОЛЖИТЕЛЕН)
curl -sS -X POST https://api.4invoices.net/v1/invoices \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: order-10457-invoice' \
  -d '{"buyer":{"name":"Exemplu Comert SRL","tax_id":"RO12345678","county":"Cluj","city":"Cluj-Napoca","country":"RO"},
       "lines":[{"description":"Abonament SaaS","quantity":1,"unit_price":400,"vat_rate":21}]}'

# 3 — preflight CIUS-RO, then send to ANAF
curl -sS -X POST https://api.4invoices.net/v1/invoices/inv_01J7ABC/efactura/validate \
  -H "Authorization: Bearer $ACCESS_TOKEN"

curl -sS -X POST https://api.4invoices.net/v1/invoices/inv_01J7ABC/efactura/submit \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H 'Idempotency-Key: efactura-inv_01J7ABC-1'
javascript
const BASE = "https://api.4invoices.net/v1";

const token = await fetch(`${BASE}/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,
  }),
}).then((r) => r.json()).then((d) => d.access_token);

const invoice = await fetch(`${BASE}/invoices`, {
  method: "POST",
  headers: {
    Authorization: `Bearer ${token}`,
    "Content-Type": "application/json",
    "Idempotency-Key": `order-${orderId}-invoice`,
  },
  body: JSON.stringify({
    buyer: { name: "Exemplu Comert SRL", tax_id: "RO12345678", county: "Cluj", city: "Cluj-Napoca", country: "RO" },
    lines: [{ description: "Abonament SaaS", quantity: 1, unit_price: 400, vat_rate: 21 }],
    currency: "RON",
    metadata: { order_id: String(orderId) },
  }),
}).then((r) => r.json());

console.log(invoice.number, invoice.totals); // "FCT-000123" { net, vat, gross }
python
import os, uuid, requests

BASE = "https://api.4invoices.net/v1"

token = requests.post(f"{BASE}/auth/token", json={
    "grant_type": "client_credentials",
    "client_id": os.environ["FOURINVOICES_CLIENT_ID"],
    "client_secret": os.environ["FOURINVOICES_CLIENT_SECRET"],
}, timeout=30).json()["access_token"]

headers = {
    "Authorization": f"Bearer {token}",
    # Еден клуч по ЛОГИЧКА операција — користете го повторно при повторување, никогаш не генерирајте нов.
    "Idempotency-Key": f"order-{order_id}-invoice",
}

invoice = requests.post(f"{BASE}/invoices", headers=headers, json={
    "buyer": {"name": "Exemplu Comert SRL", "tax_id": "RO12345678",
              "county": "Cluj", "city": "Cluj-Napoca", "country": "RO"},
    "lines": [{"description": "Abonament SaaS", "quantity": 1,
               "unit_price": 400, "vat_rate": 21}],   # процент, не 0,21
    "currency": "RON",
}, timeout=60).json()

print(invoice["number"], invoice["totals"])

Автентикација

  • Текот е client_credentials: го разменувате client_id + client_secret за Bearer access_token.
  • Токенот истекува по 3600 секунди. Освежете го пред истекот, а не при секое повикување — POST /auth/token има сопствено ограничување по IP адреса.
  • client_secret се чува само како хеш и не може да се поврати. Ако го изгубите, создајте нов акредитив и повлечете го стариот.
  • Само сервер-кон-сервер. Ниедно потекло од прелистувач не е дозволено преку CORS: од JavaScript на страница client_secret би стигнал до посетителот.
  • Scopes се доделуваат по акредитив. Издајте посебен акредитив за секој систем и дајте му минимум потребни scopes.

Фактурирање → Поставки → API пристап

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"}
javascript
// Чувајте го токенот во кеш; не создавајте по еден за секое барање.
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;
}

ДДВ-стапките се проценти

vat_rate: 21 ✓  ·  vat_rate: 0.19 ✗  ·  vat_rate: 1 ✗
  • vat_rate е ПРОЦЕНТ: 21 значи 21%.
  • Вредностите во (0, 1] се отфрлаат — 0,19 е дропка, а 1 е двосмислено помеѓу 1% и 100%.
  • 0 е валидно и значи ослободено од ДДВ или обратно плаќање (reverse charge); наведете го правниот основ во notes.
  • unit_price е НЕТО единечна цена кога издавачот е обврзник за ДДВ.
  • Ви требаат вкупните износи пред да се обврзете? POST /v1/invoices/preview-totals ја извршува истата пресметка без да потроши фискален број.

Идемпотентност при фискални запишувања

  • Заглавието Idempotency-Key е ЗАДОЛЖИТЕЛНО на POST /invoices, POST /invoices/{id}/storno и POST /invoices/{id}/efactura/submit.
  • Клучевите се памтат 24 часа. Повторување на истиот клуч со истото тело го враќа оригиналниот резултат и поставува Idempotency-Replayed: true.
  • Истиот клуч со различно тело враќа 409 idempotency_key_reuse — веќе издаден фискален документ никогаш не се презапишува.
  • Еден клуч по ЛОГИЧКА операција, а не по HTTP обид: order-10457-invoice, а не нов UUID при секое повторување.
  • Причината е правна, не техничка: искористен фискален број и фактура поднесена во националниот систем за е-фактурирање не можат да се повлечат.
bash
curl -sS -X POST https://api.4invoices.net/v1/invoices \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: order-10457-invoice' \
  -d '{
    "buyer": {
      "name": "Exemplu Comert SRL",
      "tax_id": "RO12345678",
      "address": "Str. Fabricii 12",
      "city": "Cluj-Napoca",
      "county": "Cluj",
      "country": "RO",
      "email": "[email protected]"
    },
    "lines": [
      { "description": "Abonament SaaS, iulie", "quantity": 1, "unit_price": 400, "vat_rate": 21, "unit": "buc" },
      { "description": "Implementare", "quantity": 6, "unit_price": 250, "vat_rate": 21, "unit": "ora" }
    ],
    "currency": "RON",
    "document_kind": "factura",
    "due_date": "2026-08-15",
    "metadata": { "order_id": "10457" }
  }'

Сите крајни точки

МетодКрајна точкаScopeIdempotency-KeyБарања / минута
POST/auth/token
Размена на акредитиви за access токен
——120
GET/me
Тенант, scopes и тековни ограничувања
invoices:read—120
POST/invoices
Издавање фактура или проформа
invoices:writeзадолжително30
GET/invoices
Листа на фактури (по курсор)
invoices:read—120
POST/invoices/preview-totals
Пресметка на вкупни износи без создавање на ништо
invoices:read—120
GET/invoices/{invoice_id}
Преглед на една фактура
invoices:read—120
GET/invoices/{invoice_id}/pdf
Преземање PDF на фактура
invoices:read—120
GET/invoices/{invoice_id}/xml
Преземање UBL XML (CIUS-RO за Романија)
invoices:read—120
POST/invoices/{invoice_id}/storno
Издавање кредитна фактура со која се поништува оваа фактура
invoices:writeзадолжително30
POST/invoices/{invoice_id}/payments
Означување на фактура како платена
invoices:write—30
DELETE/invoices/{invoice_id}/payments
Означување на фактура како неплатена
invoices:write—30
GET/clients
Листа на клиенти
clients:read—120
POST/clients
Создавање или ажурирање клиент
clients:write—30
GET/lookup/company/{tax_id}
Валидација на даночен број во службениот регистар (во Романија: ANAF)
clients:read—120
GET/series
Листа на нумерациски серии
invoices:read—120
POST/invoices/{invoice_id}/efactura/validate
Предпроверка — валидација без испраќање
efactura:read—120
POST/invoices/{invoice_id}/efactura/submit
Поднесување на фактура до националниот систем за е-фактурирање
efactura:submitзадолжително10
GET/efactura/submissions
Листа на поднесувања до националниот систем
efactura:read—120
GET/efactura/submissions/{submission_id}
Преглед на едно поднесување
efactura:read—120
POST/efactura/submissions/{submission_id}/refresh
Барање тековна состојба од националниот систем
efactura:read—10
GET/efactura/submissions/{submission_id}/download
Преземање архива со потпишан одговор
efactura:read—120
GET/efactura/inbox
Фактури од добавувачи примени преку националниот систем
efactura:read—120
GET/efactura/inbox/{inbound_id}
Преглед на една влезна фактура
efactura:read—120
GET/efactura/inbox/{inbound_id}/download
Преземање архива на влезна фактура
efactura:read—120
POST/efactura/inbox/sync
Повлекување нови пораки од националниот систем во сандачето
efactura:read—10

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

Scopes (опсези)

  • invoices:read
  • invoices:write
  • clients:read
  • clients:write
  • efactura:read
  • efactura:submit

Постапката на националното е-фактурирање

not_sentvalidatingqueuedsentprocessingacceptedrejectederror
  • Вистинската разлика: целата постапка на националното е-фактурирање се извршува преку API-то, а не само генерирањето документ.
  • POST /invoices/{id}/efactura/validate ја извршува предпроверката (CIUS-RO за Романија) и ги враќа точните полиња што националниот систем за е-фактурирање би ги отфрлил, без да испрати ништо.
  • GET /invoices/{id}/xml го генерира UBL XML (CIUS-RO за Романија) без поднесување — корисно за вашата сопствена архива или внатрешна ревизија.
  • POST /invoices/{id}/efactura/submit ја поднесува во националниот систем за е-фактурирање. Ова е неповратно: вториот примерок од истата фактура не може да се повлече од системот.
  • Животниот циклус е нормализиран: not_sent → validating → queued → sent → processing → accepted | rejected | error. accepted значи законски пренесено; error е транспортна грешка на наша страна и безбедно е да се повтори.
  • GET /efactura/submissions/{id}/download ја презема архивата со потпишаниот одговор од националниот систем за е-фактурирање.
  • Постои и влезната страна: POST /efactura/inbox/sync ги повлекува новите пораки од националниот систем за е-фактурирање, а GET /efactura/inbox ги наведува фактурите што ви ги испратиле вашите добавувачи.
bash
# CIUS-RO preflight. Sends NOTHING to ANAF.
curl -sS -X POST "https://api.4invoices.net/v1/invoices/inv_01J7ABC/efactura/validate?include_xml=true" \
  -H "Authorization: Bearer $ACCESS_TOKEN"

# → {"invoice_id":"inv_01J7ABC","valid":false,
#    "errors":["buyer.county is required for RO buyers"],
#    "xml_base64":"PD94bWwg..."}
bash
curl -sS -X POST https://api.4invoices.net/v1/invoices/inv_01J7ABC/efactura/submit \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H 'Idempotency-Key: efactura-inv_01J7ABC-1'

# → {"submission_id":"sub_01J7XYZ","status":"queued"}
python
import time, requests

BASE = "https://api.4invoices.net/v1"
TERMINAL = {"accepted", "rejected"}
H = {"Authorization": f"Bearer {token}"}

def wait_for_anaf(submission_id, timeout=900):
    deadline = time.time() + timeout
    delay = 15
    while time.time() < deadline:
        r = requests.get(f"{BASE}/efactura/submissions/{submission_id}", headers=H, timeout=30)
        r.raise_for_status()
        status = r.json()["status"]
        if status in TERMINAL:
            return r.json()
        # /refresh asks ANAF directly and shares the 10/min submit bucket.
        requests.post(f"{BASE}/efactura/submissions/{submission_id}/refresh", headers=H, timeout=30)
        time.sleep(delay)
        delay = min(delay * 2, 120)
    raise TimeoutError(submission_id)

Грешки

  • Секој 4xx и 5xx користи иста омотница: { "error": { type, code, message, param?, doc_url, request_id, errors? } }.
  • type е грубата класа според која се гранира ("можам ли да повторам?"), а code е стабилната причина што ја мапирате во вашиот систем.
  • param е патеката со точки до полето што прави проблем, на пример lines.0.vat_rate.
  • errors[] се појавува кога едно барање има проблеми на повеќе од едно поле.
  • request_id се бележи кај нас. Наведете го во секое барање за поддршка — тој го лоцира точното барање.
  • 404 се враќа и за id што припаѓа на друг тенант: никогаш не потврдуваме дека податоците на некој друг постојат.
json
{
  "error": {
    "type": "invalid_request_error",
    "code": "validation_failed",
    "message": "Datele trimise nu sunt valide.",
    "param": "lines.0.vat_rate",
    "doc_url": "https://4invoices.net/ro/api-dezvoltatori#errors",
    "request_id": "req_8a893f2f33d4472da8d6e144",
    "errors": [
      { "param": "lines.0.vat_rate", "message": "VAT rate must be a percent (21), not a fraction", "type": "value_error" }
    ]
  }
}
СтатусТипКод
401authentication_errormissing_bearer_token
401authentication_errorinvalid_client
401authentication_errortoken_expired
401authentication_errorinvalid_token
401authentication_errorunauthorized
403permission_errorinsufficient_scope
403permission_errorforbidden
404not_found_errornot_found
409conflict_erroridempotency_key_reuse
409conflict_erroridempotency_in_progress
409conflict_errorno_series_configured
422invalid_request_errorvalidation_failed
422invalid_request_errorissuer_profile_incomplete
422invalid_request_errorcius_ro_incomplete
422invalid_request_errorcannot_storno_draft
422invalid_request_errornothing_to_storno
422invalid_request_errorinvalid_json_number
429rate_limit_errorrate_limit_exceeded
502upstream_errorupstream_error
503service_unavailable_errorservice_unavailable

Ограничувања на брзината

Кофа (bucket)Барања / минутаЗначење
read120GET *, POST /invoices/preview-totals, POST /invoices/{id}/efactura/validate
write30POST /invoices, POST /invoices/{id}/storno, POST|DELETE /invoices/{id}/payments, POST /clients
submit10POST /invoices/{id}/efactura/submit, POST /efactura/submissions/{id}/refresh, POST /efactura/inbox/sync
  • Ограничувањата важат по акредитив, по минута: 120 за читање, 30 за запишување и 10 за операции на е-фактурирање (submit, refresh, inbox/sync).
  • X-RateLimit-Limit, X-RateLimit-Remaining и X-RateLimit-Reset се појавуваат на СЕКОЈ одговор, вклучително и 429.
  • 429 носи и Retry-After во секунди. Почитувајте го; не повторувајте во стегната јамка.
  • Кофата од 10/минута постои затоа што во Романија самата ANAF дозволува приближно едно барање за список на пораки по минута по CIF.
  • Ставете /efactura/inbox/sync на cron, а не на агресивно пратување барања.
  • Ви требаат повисоки граници? Побарајте преку Контакт со вашиот очекуван дневен обем.

X-RateLimit-Limit · X-RateLimit-Remaining · X-RateLimit-Reset · Retry-After

http
HTTP/2 429
X-RateLimit-Limit: 30
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1785312000
Retry-After: 17
Content-Type: application/json

{"error":{"type":"rate_limit_error","code":"rate_limit_exceeded",
 "message":"...","doc_url":"https://4invoices.net/ro/api-dezvoltatori#errors",
 "request_id":"req_..."}}
javascript
// Почитувајте го Retry-After; никогаш не повторувајте 4xx освен 429.
async function call(url, init, attempt = 0) {
  const res = await fetch(url, init);
  if (res.status === 429 || res.status >= 500) {
    if (attempt >= 5) return res;
    const retryAfter = Number(res.headers.get("Retry-After")) || 2 ** attempt;
    await new Promise((r) => setTimeout(r, retryAfter * 1000));
    return call(url, init, attempt + 1);
  }
  return res;
}

// Повторувањето запишување МОРА да го користи ИСТИОТ Idempotency-Key, инаку ќе издадете
// втор фискален документ.

Водичи по тема

Секоја тема има своја страница со соодветните крајни точки, примери и грешки.

Чести грешки

  • Испраќање на ДДВ како дропка (0.19) или како 1. Двете се отфрлаат; испратете 19 или 21.
  • Создавање токен по секое барање. Токенот важи еден час; чувајте го во кеш, инаку ќе ја допрете границата по IP на крајната точка за токени.
  • Создавање нов idempotency клуч при секое повторување — точно сценариото што создава дупликат фактури.
  • Повикување од JavaScript во прелистувач. CORS блокира секое потекло, а client_secret и онака би бил изложен.
  • Поднесување во националниот систем за е-фактурирање без предпроверка. Погрешното поднесување не може да се повлече.
  • Испраќање барања на /efactura/submissions/{id}/refresh повеќе од 10 пати во минута — таа кофа се дели со submit.
  • Издавање пред профилот на компанијата да биде комплетен и да постои активна фискална серија.

Прашања и одговори за програмери

Како да добијам API акредитиви за 4invoices?

Во панелот, под Фактурирање → Поставки → API пристап. Добивате client_id и client_secret; client_secret се прикажува еднаш и се чува само како хеш.

Колку време важи access_token?

3600 секунди, т.е. еден час. Чувајте го во кеш и освежете го пред истекот — крајната точка за токени има сопствено ограничување на брзината по IP адреса.

Како ја испраќам ДДВ-стапката?

Како процент: 21 за 21%. Вредностите во интервалот (0, 1] се отфрлаат, па и 0,19 и 1 не минуваат валидација.

Кои повици бараат Idempotency-Key?

POST /invoices, POST /invoices/{id}/storno и POST /invoices/{id}/efactura/submit. Клучевите се памтат 24 часа, а повторувањето го враќа оригиналниот документ со Idempotency-Replayed: true.

Можам ли да ја испратам фактурата до националниот систем за е-фактурирање директно од API-то?

Можете. Извршете предпроверка со /efactura/validate, поднесете ја со /efactura/submit, следете го нормализираниот статус на /efactura/submissions/{id} и преземете го потпишаниот одговор од системот (во Романија: ANAF).

Кои се ограничувањата на брзината?

По пристапни податоци, по минута: 120 читања, 30 запишувања и 10 операции на е-фактурирање. Секој одговор носи X-RateLimit-* заглавија, а 429 додава Retry-After.

Можам ли да го повикам API-то од мојот фронтенд?

Не. Тоа е само сервер-кон-сервер: ниедно потекло од прелистувач не минува CORS, а client_secret не треба да биде во кодот што стигнува до посетителот.

Зошто добивам issuer_profile_incomplete на мојата прва фактура?

Профилот на компанијата е нецелосен или нема активна серија од видот fiscale. Пополнете ги двете во панелот; API-то одбива да создаде невалиден фискален документ.