Publiczne API 4invoices przesyła dane fakturowe z Twojego systemu do 4invoices i prowadzi fakturę do końca w krajowym systemie e-fakturowania (w Rumunii: SPV ANAF). Jeden host produkcyjny, jeden przepływ client_credentials, idempotentne zapisy fiskalne i jedna koperta błędu na każdej trasie.
https://api.4invoices.net/v1 · Specyfikacja OpenAPI (bez klucza)
Czym jest API 4invoices
- Publiczne API 4invoices to REST przez HTTPS: JSON na wejściu i JSON na wyjściu.
- Jeden host produkcyjny: https://api.4invoices.net/v1. Nie ma osobnego hosta testowego.
- Specyfikacja OpenAPI 3.1 jest publiczna i nie wymaga klucza — zaimportuj ją bezpośrednio do Postmana lub generatora klientów.
- Każda odpowiedź zawiera nagłówek X-Request-Id; podawaj go, gdy zgłaszasz się do wsparcia.
- Każdy zapis fiskalny jest idempotentny: ponowienie po przekroczeniu limitu czasu nigdy nie wystawia drugiego dokumentu.
- Pełny obieg krajowego systemu e-fakturowania przez API (w Rumunii: ANAF e-Factura): walidacja wstępna, wysyłka, śledzenie, podpisana odpowiedź i skrzynka przychodząca. Żaden rumuński konkurent tego nie udostępnia.
- SmartBill nie ma żadnego endpointu do wysyłki do krajowego systemu e-fakturowania (ANAF, w Rumunii) — wysyłka to ustawienie konta dostępne tylko w interfejsie — i w ogóle nie ma endpointu do listowania faktur.
- Oblio potrafi tworzyć klientów i produkty jedynie jako efekt uboczny wystawienia dokumentu, a nie jako pełnoprawne zasoby.
- Generowanie XML UBL / CIUS-RO (Rumunia) bez wysyłki: dostajesz dokument prawny jako plik, nie dotykając krajowego systemu e-fakturowania.
- Idempotencja zapisów fiskalnych: ponowienie po przekroczeniu limitu czasu nie tworzy drugiej faktury ani nie zużywa drugiego numeru.
- Programowo odczytywalna skrzynka faktur od dostawców — podstawa automatycznego uzgadniania.
Pierwsza faktura w 6 krokach
- Wygeneruj poświadczenie w panelu, w Fakturowanie → Ustawienia → Dostęp API, i skopiuj client_secret — jest pokazywany tylko raz.
- Uzupełnij profil firmy i aktywuj co najmniej jedną serię typu fiscale, inaczej wystawienie zwróci issuer_profile_incomplete lub no_series_configured.
- Wygeneruj poświadczenie w panelu, w Fakturowanie → Ustawienia → Dostęp API, i skopiuj client_secret — jest pokazywany tylko raz.
- Uzupełnij profil firmy i aktywuj co najmniej jedną serię typu fiscale, inaczej wystawienie zwróci issuer_profile_incomplete lub no_series_configured.
- Wymień poświadczenie na token: POST /v1/auth/token z grant_type client_credentials. Token żyje godzinę — buforuj go, nie pobieraj nowego przy każdym żądaniu.
- Wystaw fakturę: POST /v1/invoices z nagłówkiem Idempotency-Key i vat_rate jako procentem (21, nie 0.21).
- Uruchom walidację (CIUS-RO dla Rumunii) przez POST /v1/invoices/{id}/efactura/validate, a potem wyślij do krajowego systemu e-fakturowania (w Rumunii: ANAF) przez POST /v1/invoices/{id}/efactura/submit.
- Śledź stan przez GET /v1/efactura/submissions/{id}, aż osiągnie accepted lub rejected.
bash
# 1 — token (1 godzina, zapisz w cache)
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 — wystaw fakturę (Idempotency-Key jest WYMAGANY)
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}",
# Jeden klucz na LOGICZNĄ operację — używaj go ponownie przy ponowieniu, nigdy nie generuj go na nowo.
"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}], # procent, nie 0.21
"currency": "RON",
}, timeout=60).json()
print(invoice["number"], invoice["totals"])Uwierzytelnianie
- Przepływ to client_credentials: wymieniasz client_id + client_secret na access_token typu Bearer.
- Token wygasa po 3600 sekundach. Odświeżaj go przed wygaśnięciem, a nie przy każdym wywołaniu — POST /auth/token ma własny limit na adres IP.
- client_secret jest przechowywany wyłącznie jako skrót i nie da się go odzyskać. Jeśli go zgubisz, generujesz nowe poświadczenie i unieważniasz stare.
- Wyłącznie komunikacja serwer–serwer. CORS nie dopuszcza żadnego źródła przeglądarkowego: w JavaScripcie strony sekret wyciekłby do odwiedzającego.
- Zakresy (scopes) przyznaje się dla każdego poświadczenia osobno. Wydaj osobne poświadczenie dla każdego systemu i nadaj mu tylko minimalne zakresy, których potrzebuje.
Fakturowanie → Ustawienia → Dostęp 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
// Zapisz token w cache; nie generuj nowego przy każdym żądaniu.
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;
}Stawki VAT to procenty
vat_rate: 21 ✓ · vat_rate: 0.19 ✗ · vat_rate: 1 ✗- vat_rate to PROCENT: 21 oznacza 21%.
- Wartości z przedziału (0, 1] są odrzucane — 0.19 to ułamek, a 1 jest niejednoznaczne między 1% a 100%.
- 0 jest poprawne i oznacza zwolnienie lub odwrotne obciążenie; wpisz podstawę prawną w notes.
- unit_price to cena jednostkowa NETTO, gdy wystawca jest płatnikiem VAT.
- Potrzebujesz sum przed zatwierdzeniem? POST /v1/invoices/preview-totals wykonuje te same obliczenia bez zużywania numeru fiskalnego.
Idempotentność zapisów fiskalnych
- Nagłówek Idempotency-Key jest WYMAGANY w POST /invoices, POST /invoices/{id}/storno i POST /invoices/{id}/efactura/submit.
- Klucze są pamiętane przez 24 godziny. Ponowne użycie tego samego klucza z tą samą treścią zwraca pierwotny wynik i ustawia Idempotency-Replayed: true.
- Ten sam klucz z inną treścią zwraca 409 idempotency_key_reuse — już wystawiony dokument fiskalny nigdy nie zostaje nadpisany.
- Jeden klucz na operację LOGICZNĄ, a nie na próbę HTTP: order-10457-invoice, a nie nowy UUID przy każdym ponowieniu.
- Powód jest prawny, nie techniczny: zużytego numeru fiskalnego ani faktury złożonej w krajowym systemie e-fakturowania nie da się wycofać.
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" }
}'Wszystkie endpointy
| Metoda | Endpoint | Scope | Idempotency-Key | Żądania / minutę |
|---|---|---|---|---|
| POST | /auth/token Wymień dane uwierzytelniające na token dostępu | — | — | 120 |
| GET | /me Tenant, scope'y i bieżące limity | invoices:read | — | 120 |
| POST | /invoices Wystaw fakturę lub proformę | invoices:write | wymagane | 30 |
| GET | /invoices Wyświetl faktury (stronicowanie kursorem) | invoices:read | — | 120 |
| POST | /invoices/preview-totals Oblicz sumy bez tworzenia czegokolwiek | invoices:read | — | 120 |
| GET | /invoices/{invoice_id} Pobierz jedną fakturę | invoices:read | — | 120 |
| GET | /invoices/{invoice_id}/pdf Pobierz PDF faktury | invoices:read | — | 120 |
| GET | /invoices/{invoice_id}/xml Pobierz XML UBL / CIUS-RO (Rumunia) | invoices:read | — | 120 |
| POST | /invoices/{invoice_id}/storno Wystaw fakturę korygującą anulującą tę fakturę | invoices:write | wymagane | 30 |
| POST | /invoices/{invoice_id}/payments Oznacz fakturę jako opłaconą | invoices:write | — | 30 |
| DELETE | /invoices/{invoice_id}/payments Oznacz fakturę jako nieopłaconą | invoices:write | — | 30 |
| GET | /clients Wyświetl klientów | clients:read | — | 120 |
| POST | /clients Utwórz lub zaktualizuj klienta | clients:write | — | 30 |
| GET | /lookup/company/{tax_id} Zweryfikuj CUI w rejestrze ANAF (Rumunia) | clients:read | — | 120 |
| GET | /series Wyświetl serie numeracji | invoices:read | — | 120 |
| POST | /invoices/{invoice_id}/efactura/validate Wstępna walidacja (CIUS-RO dla Rumunii) — sprawdzenie bez wysyłki | efactura:read | — | 120 |
| POST | /invoices/{invoice_id}/efactura/submit Wyślij fakturę do ANAF (SPV, Rumunia) | efactura:submit | wymagane | 10 |
| GET | /efactura/submissions Wyświetl zgłoszenia do ANAF (Rumunia) | efactura:read | — | 120 |
| GET | /efactura/submissions/{submission_id} Pobierz jedno zgłoszenie | efactura:read | — | 120 |
| POST | /efactura/submissions/{submission_id}/refresh Zapytaj ANAF (Rumunia) o aktualny stan | efactura:read | — | 10 |
| GET | /efactura/submissions/{submission_id}/download Pobierz podpisane archiwum odpowiedzi | efactura:read | — | 120 |
| GET | /efactura/inbox Faktury od dostawców otrzymane przez SPV (Rumunia) | efactura:read | — | 120 |
| GET | /efactura/inbox/{inbound_id} Pobierz jedną otrzymaną fakturę | efactura:read | — | 120 |
| GET | /efactura/inbox/{inbound_id}/download Pobierz archiwum otrzymanej faktury | efactura:read | — | 120 |
| POST | /efactura/inbox/sync Pobierz nowe wiadomości SPV (Rumunia) do skrzynki | efactura:read | — | 10 |
25 endpoints · https://api.4invoices.net/v1/openapi.json
Scope'y
- invoices:read
- invoices:write
- clients:read
- clients:write
- efactura:read
- efactura:submit
Krajowe e-fakturowanie (ANAF e-Factura, Rumunia)
- Prawdziwy wyróżnik: cały obieg krajowego systemu e-fakturowania (w Rumunii: ANAF e-Factura) przebiega przez API, a nie tylko generowanie dokumentu.
- POST /invoices/{id}/efactura/validate uruchamia walidację wstępną (CIUS-RO dla Rumunii) i zwraca dokładnie te pola, które krajowy system e-fakturowania by odrzucił, niczego nie wysyłając.
- GET /invoices/{id}/xml generuje XML UBL / CIUS-RO (Rumunia) bez wysyłki — przydatne do własnego archiwum lub audytu wewnętrznego.
- POST /invoices/{id}/efactura/submit składa fakturę w krajowym systemie e-fakturowania (w Rumunii: SPV ANAF). Operacja jest nieodwracalna: drugiej kopii tej samej faktury nie da się już wycofać.
- Cykl życia jest znormalizowany: not_sent → validating → queued → sent → processing → accepted | rejected | error. accepted oznacza przekazanie ze skutkiem prawnym; error to błąd transportu po naszej stronie i można go bezpiecznie ponowić.
- GET /efactura/submissions/{id}/download pobiera archiwum z podpisaną odpowiedzią ANAF (Rumunia).
- Istnieje też strona przychodząca: POST /efactura/inbox/sync pobiera nowe wiadomości z SPV (Rumunia), a GET /efactura/inbox wyświetla faktury wysłane Ci przez dostawców.
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)Błędy
- Każdy 4xx i 5xx używa tej samej koperty: { "error": { type, code, message, param?, doc_url, request_id, errors? } }.
- type to ogólna klasa, według której rozgałęziasz logikę („czy mogę ponowić?”), a code to stabilna przyczyna, którą mapujesz na własny system.
- param to ścieżka z kropkami do błędnego pola, na przykład lines.0.vat_rate.
- errors[] pojawia się, gdy jedno żądanie ma problemy w więcej niż jednym polu.
- request_id jest logowany po naszej stronie. Podawaj go w każdym zgłoszeniu do wsparcia — wskazuje dokładnie to żądanie.
- 404 jest zwracane także dla id należącego do innego tenanta: nigdy nie potwierdzamy, że cudze dane istnieją.
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" }
]
}
}| Status | Typ | Kod |
|---|---|---|
| 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 |
Limity żądań
| Bucket | Żądania / minutę | Znaczenie |
|---|---|---|
| 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 |
- Limity liczone są na poświadczenie, na minutę: 120 dla odczytów, 30 dla zapisów, 10 dla operacji e-fakturowania (submit, refresh, inbox/sync).
- X-RateLimit-Limit, X-RateLimit-Remaining i X-RateLimit-Reset pojawiają się w KAŻDEJ odpowiedzi, łącznie z 429.
- 429 zawiera też Retry-After w sekundach. Respektuj go; nie ponawiaj w ciasnej pętli.
- Pula 10/min istnieje, ponieważ w Rumunii sam ANAF pozwala na mniej więcej jedno wywołanie listy wiadomości na minutę na CIF.
- Uruchamiaj /efactura/inbox/sync z crona, a nie w agresywnym odpytywaniu.
- Potrzebujesz wyższych limitów? Napisz przez stronę Kontakt, podając oczekiwany wolumen dzienny.
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
// Respektuj Retry-After; nigdy nie ponawiaj 4xx innego niż 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;
}
// Ponowna próba zapisu MUSI użyć TEGO SAMEGO Idempotency-Key, inaczej wystawisz
// drugi dokument fiskalny.Przewodniki tematyczne
Każdy temat ma własną stronę z odpowiednimi endpointami, przykładami i błędami.
- Uwierzytelnianie w API 4invoicesWymieniasz client_id i client_secret na godzinny token Bearer, wyłącznie server-to-server.
- Wystawianie faktur przez APIPOST /invoices z Idempotency-Key, VAT w procentach, plus lista, korekta i oznaczenie jako zapłacona.
- Klienci i weryfikacja w rejestrze ANAF (Rumunia)Wstawienie lub aktualizacja (upsert) z deduplikacją po tax_id oraz sprawdzenie w rejestrze ANAF (Rumunia), aby e-faktura nie została odrzucona.
- ANAF e-Factura (Rumunia) przez APIDla Rumunii: walidacja CIUS-RO, wysyłka do SPV, znormalizowany status, podpisana odpowiedź i skrzynka faktur od dostawców.
- Serie numeracji w APIBez aktywnej serii typu fiscale wystawienie zwraca 409 no_series_configured.
- Kody błędów i koperta błęduJeden format na wszystkich trasach: type, code, message, param, doc_url, request_id i errors.
- Limity żądań i ponowne próby120 odczytów, 30 zapisów i 10 operacji e-fakturowania na minutę na poświadczenie, z nagłówkami X-RateLimit-*.
Częste błędy
- Wysyłanie VAT jako ułamka (0.19) lub jako 1. Oba są odrzucane; wyślij 19 lub 21.
- Generowanie tokenu przy każdym żądaniu. Token jest ważny godzinę; przechowuj go w pamięci podręcznej, inaczej trafisz na limit na IP endpointu tokenu.
- Generowanie nowego klucza idempotencji przy każdym ponowieniu — dokładnie ten scenariusz tworzy zduplikowane faktury.
- Wywoływanie z JavaScriptu w przeglądarce. CORS blokuje każde źródło, a sekret i tak zostałby ujawniony.
- Składanie w krajowym systemie e-fakturowania bez walidacji wstępnej (w Rumunii: CIUS-RO). Błędnej wysyłki nie da się wycofać.
- Odpytywanie /efactura/submissions/{id}/refresh częściej niż 10 razy na minutę — ta pula jest współdzielona z submit.
- Wystawianie, zanim profil firmy jest kompletny i istnieje aktywna seria fiskalna.
Pytania i odpowiedzi dla programistów
Jak uzyskać dane dostępowe do API 4invoices?
W panelu, w Fakturowanie → Ustawienia → Dostęp API. Otrzymujesz client_id i client_secret; secret jest pokazywany raz i przechowywany tylko jako hash.
Jak długo działa access_token?
3600 sekund, czyli godzinę. Zapisz go w cache i odświeżaj przed wygaśnięciem — endpoint tokenu ma własny limit na IP.
Jak przesłać stawkę VAT?
Jako procent: 21 dla 21%. Wartości z zakresu (0, 1] są odrzucane, więc 0.19 i 1 nie przechodzą walidacji.
Które wywołania wymagają Idempotency-Key?
POST /invoices, POST /invoices/{id}/storno i POST /invoices/{id}/efactura/submit. Klucze są pamiętane 24 godziny, a powtórzenie zwraca oryginalny dokument z Idempotency-Replayed: true.
Czy mogę wysłać fakturę do ANAF e-Factura (Rumunia) bezpośrednio z API?
Tak, w przypadku Rumunii: wstępna walidacja CIUS-RO przez /efactura/validate, wysyłka przez /efactura/submit, śledzenie znormalizowanego statusu na /efactura/submissions/{id} i pobranie podpisanej odpowiedzi ANAF.
Jakie są limity żądań?
Na poświadczenie, na minutę: 120 odczytów, 30 zapisów i 10 operacji e-fakturowania. Każda odpowiedź zawiera nagłówki X-RateLimit-*, a 429 dodaje Retry-After.
Czy mogę wywoływać API z frontendu?
Nie. Jest wyłącznie server-to-server: żaden origin przeglądarki nie przechodzi CORS, a client_secret nie ma czego szukać w kodzie docierającym do użytkownika.
Dlaczego dostaję issuer_profile_incomplete przy pierwszej fakturze?
Profil firmy jest niekompletny lub nie ma aktywnej serii typu fiscale. Uzupełnij oba w panelu; API nie wystawi nieprawidłowego dokumentu fiskalnego.