Јавни 4invoices API шаље податке о фактурисању из вашег система у 4invoices и води фактуру до краја у националном систему е-фактурисања (у Румунији: ANAF-ов SPV). Један продукцијски хост, један client_credentials ток, идемпотентни фискални записи и исти омот грешке на свим рутама.
https://api.4invoices.net/v1 · OpenAPI спецификација (без кључа)
Шта је 4invoices API
- Јавни 4invoices API је REST преко HTTPS-а: JSON на улазу и JSON на излазу.
- Постоји један продукцијски host: https://api.4invoices.net/v1. Засебан тест-хост не постоји.
- OpenAPI 3.1 спецификација је јавна и доступна без кључа — увезите је директно у Postman или у генератор клијената.
- Сваки одговор садржи заглавље X-Request-Id; наведите га када се обраћате подршци.
- Сваки фискални упис је идемпотентан: поновни покушај после истека времена никада не издаје други документ.
- Цео круг националног система е-фактурисања преко API-ја (у Румунији: ANAF e-Factura): провера, предаја, праћење, потписани одговор и улазно сандуче. Ниједан румунски конкурент то не нуди.
- SmartBill нема ниједан ендпоинт за предају у национални систем е-фактурисања (ANAF, у Румунији) — слање је само подешавање налога у интерфејсу — и уопште нема ендпоинт за листу фактура.
- Oblio може да прави клијенте и производе само као успутни ефекат издавања документа, а не као самосталне ресурсе.
- Генерисање UBL / CIUS-RO XML-а (Румунија) без предаје: правни документ добијате као датотеку, а да не дирате национални систем е-фактурисања.
- Идемпотентност фискалних уписа: поновни покушај после истека времена не прави другу фактуру нити троши други број.
- Програмски читљиво сандуче улазних фактура добављача — основа за аутоматско усаглашавање.
Прва фактура у 6 корака
- Генеришите креденцијал у панелу, у Фактурисање → Подешавања → API приступ, и копирајте client_secret — приказује се само једном.
- Попуните профил фирме и активирајте бар једну серију типа fiscale, иначе издавање враћа issuer_profile_incomplete или no_series_configured.
- Генеришите креденцијал у панелу, у Фактурисање → Подешавања → API приступ, и копирајте client_secret — приказује се само једном.
- Попуните профил фирме и активирајте бар једну серију типа fiscale, иначе издавање враћа issuer_profile_incomplete или no_series_configured.
- Замените креденцијал за токен: POST /v1/auth/token са grant_type client_credentials. Токен траје један сат — кеширајте га, немојте узимати нови за сваки захтев.
- Издајте фактуру: POST /v1/invoices са заглављем Idempotency-Key и vat_rate као процентом (21, не 0.21).
- Покрените проверу (CIUS-RO за Румунију) са POST /v1/invoices/{id}/efactura/validate, па пошаљите националном систему е-фактурисања (у Румунији: ANAF) са POST /v1/invoices/{id}/efactura/submit.
- Пратите стање са 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-а на страници тајна би процурела посетиоцу.
- Scope-ови се додељују по креденцијалу. Издајте засебан креденцијал за сваки систем и сваком доделите само минималне scope-ове који су му потребни.
Фактурисање → Подешавања → 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 је исправна вредност и значи ослобођење или обрнуто обрачунавање; законску напомену упишите у белешке.
- 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" }
}'Сви endpoint-и
| Метода | Endpoint | Scope | Idempotency-Key | Захтева / минут |
|---|---|---|---|---|
| POST | /auth/token Замени креденцијале за токен приступа | — | — | 120 |
| GET | /me Тенант, scope-ови и тренутни лимити | 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 / CIUS-RO XML (Румунија) | 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} Провери CUI у ANAF регистру (Румунија) | clients:read | — | 120 |
| GET | /series Листа серија нумерисања | invoices:read | — | 120 |
| POST | /invoices/{invoice_id}/efactura/validate Предпровера (CIUS-RO за Румунију) — валидација без слања | efactura:read | — | 120 |
| POST | /invoices/{invoice_id}/efactura/submit Пошаљи фактуру ANAF-у (SPV, Румунија) | efactura:submit | обавезно | 10 |
| GET | /efactura/submissions Листа слања ANAF-у (Румунија) | efactura:read | — | 120 |
| GET | /efactura/submissions/{submission_id} Преузми једно слање | efactura:read | — | 120 |
| POST | /efactura/submissions/{submission_id}/refresh Упит ANAF-у (Румунија) о тренутном стању | efactura:read | — | 10 |
| GET | /efactura/submissions/{submission_id}/download Преузми архиву потписаног одговора | efactura:read | — | 120 |
| GET | /efactura/inbox Фактуре добављача примљене преко SPV-а (Румунија) | efactura:read | — | 120 |
| GET | /efactura/inbox/{inbound_id} Преузми једну улазну фактуру | efactura:read | — | 120 |
| GET | /efactura/inbox/{inbound_id}/download Преузми архиву примљене фактуре | efactura:read | — | 120 |
| POST | /efactura/inbox/sync Преузми нове SPV поруке (Румунија) у сандуче | efactura:read | — | 10 |
25 endpoints · https://api.4invoices.net/v1/openapi.json
Scope-ови
- invoices:read
- invoices:write
- clients:read
- clients:write
- efactura:read
- efactura:submit
Национално е-фактурисање (ANAF e-Factura, Румунија)
- Оно што нас заиста издваја: цео круг националног система е-фактурисања (у Румунији: ANAF e-Factura) одвија се преко API-ја, а не само генерисање документа.
- POST /invoices/{id}/efactura/validate покреће проверу (CIUS-RO за Румунију) и враћа тачно она поља која би национални систем е-фактурисања одбио, без икаквог слања.
- GET /invoices/{id}/xml генерише UBL / CIUS-RO XML (Румунија) без предаје — корисно за сопствену архиву или интерну ревизију.
- POST /invoices/{id}/efactura/submit предаје фактуру у национални систем е-фактурисања (у Румунији: ANAF-ов SPV). То је неповратно: други примерак исте фактуре више се не може повући.
- Животни циклус је нормализован: not_sent → валидација → у реду → послат → обрада → accepted | rejected | грешка. accepted значи да је фактура правно пренета; грешка је грешка преноса на нашој страни и поновни покушај је безбедан.
- GET /efactura/submissions/{id}/download преузима архиву са потписаним одговором ANAF-а (Румунија).
- Постоји и улазна страна: POST /efactura/inbox/sync повлачи нове поруке из SPV-а (Румунија), а 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, грешки? } }.
- type је груба класа по којој гранате логику („могу ли да поновим?”), а code је стабилан разлог који мапирате у сопствени систем.
- param је путања проблематичног поља у нотацији са тачкама, на пример lines.0.vat_rate.
- грешке[] се појављује када један захтев има проблеме у више поља.
- request_id бележимо на нашој страни. Наведите га у сваком захтеву за подршку — он једнозначно одређује захтев.
- 404 се враћа и за id који припада другом tenant-у: никада не потврђујемо да туђи подаци постоје.
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" }
]
}
}| Статус | Тип | Код |
|---|---|---|
| 401 | authentication_error | missing_bearer_token |
| 401 | authentication_error | invalid_client |
| 401 | authentication_error | token_expired |
| 401 | authentication_error | invalid_token |
| 401 | authentication_error | unauthorized |
| 403 | permission_error | insufficient_scope |
| 403 | permission_error | forbidden |
| 404 | not_found_error | not_found |
| 409 | conflict_error | idempotency_key_reuse |
| 409 | conflict_error | idempotency_in_progress |
| 409 | conflict_error | no_series_configured |
| 422 | invalid_request_error | validation_failed |
| 422 | invalid_request_error | issuer_profile_incomplete |
| 422 | invalid_request_error | cius_ro_incomplete |
| 422 | invalid_request_error | cannot_storno_draft |
| 422 | invalid_request_error | nothing_to_storno |
| 422 | invalid_request_error | invalid_json_number |
| 429 | rate_limit_error | rate_limit_exceeded |
| 502 | upstream_error | upstream_error |
| 503 | service_unavailable_error | service_unavailable |
Ограничења захтева
| Bucket | Захтева / минут | Значење |
|---|---|---|
| read | 120 | GET *, POST /invoices/preview-totals, POST /invoices/{id}/efactura/validate |
| write | 30 | POST /invoices, POST /invoices/{id}/storno, POST|DELETE /invoices/{id}/payments, POST /clients |
| submit | 10 | POST /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/min постоји зато што у Румунији сам ANAF дозвољава отприлике један позив за листу порука у минуту по CIF-у.
- /efactura/inbox/sync покрећите из cron-а, а не агресивним polling-ом.
- Потребне су вам веће границе? Јавите нам се преко странице Контакт и наведите очекивани дневни обим.
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, иначе издајете
// други фискални документ.Водичи по темама
Свака тема има своју страницу са одговарајућим endpoint-има, примерима и грешкама.
- Аутентикација на 4invoices APIМењате client_id и client_secret за Bearer токен који важи сат, само сервер-до-сервер.
- Издавање фактура преко API-јаPOST /invoices са Idempotency-Key, ПДВ у процентима, уз листу, сторно и означавање плаћеног.
- Клијенти и провера у ANAF регистру (Румунија)Уметање или ажурирање са дедупликацијом по tax_id и провера у ANAF регистру (Румунија) да е-фактура не буде одбијена.
- ANAF e-Factura (Румунија) преко API-јаЗа Румунију: CIUS-RO провера, предаја у SPV, нормализовани статус, потписани одговор и пристигле фактуре.
- Серије нумерисања у API-јуБез активне серије типа fiscale издавање враћа 409 no_series_configured.
- Кодови грешака и омот грешкеЈедан облик на свим рутама: type, code, message, param, doc_url, request_id и грешке.
- Ограничења захтева и поновни покушаји120 читања, 30 писања и 10 операција е-фактурисања у минути по credential-у, са X-RateLimit-* заглављима.
Честе грешке
- Слање ПДВ-а као разломка (0.19) или као 1. Обоје се одбија; пошаљите 19 или 21.
- Узимање токена за сваки захтев. Токен траје сат времена; кеширајте га, иначе ћете ударити у ограничење по IP адреси на endpoint-у за токен.
- Генерисање новог кључа идемпотентности при сваком поновном покушају — управо сценарио који прави дуплиране фактуре.
- Позивање из JavaScript-а у прегледачу. CORS блокира свако порекло, а тајна би ионако била изложена.
- Предаја у национални систем е-фактурисања без провере (у Румунији: CIUS-RO). Погрешна предаја не може се повући.
- Позивање /efactura/submissions/{id}/refresh више од 10 пута у минуту — тај корпа се дели са шаљем.
- Издавање пре него што је профил фирме попуњен и постоји активна фискална серија.
Питања и одговори за програмере
Како да добијем приступне податке за 4invoices API?
У панелу, у Фактурисање → Подешавања → API приступ. Добијате client_id и 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.
Могу ли фактуру да пошаљем у ANAF e-Factura (Румунија) директно преко API-ја?
Да, за Румунију: CIUS-RO провера преко /efactura/validate, предаја преко /efactura/submit, праћење нормализованог статуса на /efactura/submissions/{id} и преузимање ANAF-овог потписаног одговора.
Која су ограничења захтева?
По приступном налогу, по минуту: 120 читања, 30 писања и 10 операција е-фактурисања. Сваки одговор носи X-RateLimit-* заглавља, а 429 додаје Retry-After.
Могу ли да зовем API из frontend-а?
Не. Искључиво је server-to-server: ниједан извор прегледача не прође CORS, а client_secret не припада коду који долази до посетиоца.
Зашто добијам issuer_profile_incomplete на првој фактури?
Профил фирме није потпун или нема активне серије типа fiscale. Допуните оба у панелу; API одбија да произведе неважећи фискални документ.