4invoices API за разработчици

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

Публичното API на 4invoices изпраща данните за фактуриране от вашата система към 4invoices и придвижва фактурата докрай в националната система за е-фактуриране (в Румъния SPV на ANAF). Един продукционен хост, един client_credentials поток, идемпотентни фискални записи и един и същ формат на грешките по всички маршрути.

https://api.4invoices.net/v1 · OpenAPI спецификация (без ключ)

Какво е 4invoices API

  • Публичният API на 4invoices е REST през HTTPS, с JSON на входа и JSON на изхода.
  • Един продукционен хост: https://api.4invoices.net/v1. Отделен тестов хост няма.
  • Спецификацията OpenAPI 3.1 е публична и без ключ — импортирайте я директно в Postman или в генератор на клиенти.
  • Всеки отговор съдържа заглавка X-Request-Id; посочвайте я, когато се обръщате към поддръжката.
  • Всеки фискален запис е идемпотентен: повторен опит след таймаут никога не издава втори документ.
Само сървър-към-сървър. Никога не извиквайте API от браузърен JavaScript: client_secret ще стигне до посетителя, а CORS блокира всеки източник.
  • Пълният румънски цикъл с ANAF e-Factura през API: предварителна проверка, подаване, проследяване, подписан отговор и входяща пощенска кутия. Нито един румънски конкурент не предлага това.
  • SmartBill няма нито един ендпойнт за подаване в националната система за е-фактуриране (ANAF, в Румъния) — изпращането към SPV е настройка на акаунта само в интерфейса — и изобщо няма ендпойнт за списък с фактури.
  • Oblio може да създава клиенти и продукти само като страничен ефект от издаването на документ, а не като самостоятелни ресурси.
  • Генериране на XML UBL (в Румъния 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 и изпратете фактурата към националната система за е-фактуриране (в Румъния ANAF) с 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 не се допуска нито един браузър origin: от JavaScript в страницата тайната би изтекла към посетителя.
  • Обхвати се предоставят за всеки комплект идентификационни данни поотделно. Издайте отделни идентификационни данни за всяка система и дайте на всяка минималните обхвати, от които се нуждае.

Фактуриране → Настройки → 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 при всеки повторен опит.
  • Причината е правна, не техническа: изразходван фискален номер и фактура, подадена в националната система за е-фактуриране (в Румъния SPV), не могат да бъдат оттеглени.
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-и

МетодEndpointScopeIdempotency-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 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}
Валидиране на 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
Списък с подаванията към националната система за е-фактуриране
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

Scope-ове

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

Пътят до националната система за е-фактуриране

not_sentvalidatingqueuedsentprocessingacceptedrejectederror
  • Истинското предимство: целият цикъл с националната система за е-фактуриране (в Румъния ANAF e-Factura) минава през API, а не само генерирането на документа.
  • POST /invoices/{id}/efactura/validate изпълнява предварителната проверка (в Румъния CIUS-RO) и връща точно полетата, които националната система за е-фактуриране би отхвърлила, без да изпраща нищо.
  • GET /invoices/{id}/xml генерира XML във формат UBL (в Румъния CIUS-RO) без подаване — полезно за собствен архив или вътрешен одит.
  • POST /invoices/{id}/efactura/submit я подава в националната система за е-фактуриране (в Румъния 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" }
    ]
  }
}
СтатусТипКод
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/min съществува, защото в Румъния 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, иначе издавате
// втори фискален документ.

Ръководства по тема

Всяка тема има собствена страница със съответните endpoint-и, примери и грешки.

Често срещани грешки

  • Изпращане на ДДС като дроб (0.19) или като 1. И двете се отхвърлят; изпращайте 19 или 21.
  • Създаване на токен за всяка заявка. Токенът е валиден един час; кеширайте го, иначе ще ударите лимита по IP на токен endpoint-а.
  • Генериране на нов идемпотентност ключ при всеки повторен опит — точно сценарият, който създава дублирани фактури.
  • Извикване от JavaScript в браузъра. CORS блокира всеки източник, а тайната така или иначе би била разкрита.
  • Подаване в националната система за е-фактуриране без предварителната проверка (в Румъния CIUS-RO). Грешно подаване не може да бъде оттеглено.
  • Полинг на /efactura/submissions/{id}/refresh повече от 10 пъти в минута — тази кошница е обща със изпращам.
  • Издаване, преди профилът на фирмата да е попълнен и да съществува активна фискална серия.

Въпроси и отговори за разработчици

Как получавам данни за достъп до 4invoices API?

В панела, в Фактуриране → Настройки → API достъп. Получавате client_id и client_secret; secret-ът се показва еднократно и се пази само като хеш.

Колко време е валиден access_token?

3600 секунди, тоест един час. Кеширайте го и го подновявайте преди изтичане — токен endpoint-ът има собствен лимит по 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 (в Румъния CIUS-RO), подаване с /efactura/submit, следене на нормализирания статус на /efactura/submissions/{id} и изтегляне на подписания отговор на системата (в Румъния ANAF).

Какви са лимитите на заявките?

На идентификационни данни, за минута: 120 четения, 30 записа и 10 операции с националната система за е-фактуриране. Всеки отговор носи X-RateLimit-* заглавки, а 429 добавя Retry-After.

Мога ли да извиквам API от фронтенда?

Не. Само сървър-към-сървър е: никакъв браузърен източник не минава CORS, а client_secret няма място в код, който стига до посетителя.

Защо получавам issuer_profile_incomplete на първата фактура?

Профилът на фирмата е непълен или няма активна серия от тип fiscale. Допълнете ги в панела; API не издава невалиден фискален документ.