API công khai của 4invoices đưa dữ liệu hóa đơn từ hệ thống của bạn vào 4invoices và đưa hóa đơn đi hết chặng qua hệ thống hóa đơn điện tử quốc gia (tại Romania: SPV của ANAF). Một máy chủ sản xuất, một luồng client_credentials, các thao tác ghi hóa đơn có tính idempotent và một khung lỗi thống nhất trên mọi tuyến.
https://api.4invoices.net/v1 · Đặc tả OpenAPI (không cần khóa)
API 4invoices là gì
- API công khai của 4invoices là REST qua HTTPS, nhận JSON và trả về JSON.
- Một máy chủ sản xuất duy nhất: https://api.4invoices.net/v1. Không có máy chủ sandbox riêng.
- Đặc tả OpenAPI 3.1 là công khai và không cần khóa — nhập trực tiếp vào Postman hoặc công cụ sinh mã client.
- Mọi phản hồi đều có header X-Request-Id; hãy cung cấp mã này khi bạn yêu cầu hỗ trợ.
- Mọi thao tác ghi liên quan đến tài chính đều có tính idempotent: thử lại sau khi hết thời gian chờ sẽ không bao giờ phát hành chứng từ thứ hai.
- Toàn bộ chu trình e-Factura ANAF (Romania) qua API: kiểm tra sơ bộ, nộp, theo dõi, phản hồi có chữ ký và hộp thư đến. Không đối thủ nào tại Romania cung cấp đầy đủ điều này.
- SmartBill không có endpoint nào để nộp lên hệ thống hóa đơn điện tử quốc gia (ANAF, tại Romania) — việc gửi chỉ là một cài đặt tài khoản trên giao diện — và hoàn toàn không có endpoint liệt kê hóa đơn.
- Oblio chỉ có thể tạo khách hàng và sản phẩm như một hệ quả phụ khi phát hành chứng từ, chứ không phải như các tài nguyên hạng nhất.
- Tạo XML UBL (CIUS-RO cho Romania) mà không cần nộp: bạn nhận được chứng từ pháp lý dưới dạng tệp mà không cần chạm vào hệ thống hóa đơn điện tử quốc gia.
- Tính idempotent cho thao tác ghi tài chính: thử lại sau khi hết thời gian chờ không tạo ra hóa đơn thứ hai hay tiêu tốn thêm một số.
- Hộp thư đến hóa đơn nhà cung cấp có thể đọc bằng lập trình — nền tảng cho đối soát tự động.
Hóa đơn đầu tiên trong 6 bước
- Tạo thông tin xác thực trong bảng điều khiển tại Lập hóa đơn → Cài đặt → Truy cập API, rồi sao chép client_secret — secret chỉ hiển thị một lần.
- Hoàn tất hồ sơ doanh nghiệp và kích hoạt ít nhất một dãy số thuộc loại fiscale, nếu không việc phát hành sẽ trả về issuer_profile_incomplete hoặc no_series_configured.
- Tạo thông tin xác thực trong bảng điều khiển tại Lập hóa đơn → Cài đặt → Truy cập API, rồi sao chép client_secret — secret chỉ hiển thị một lần.
- Hoàn tất hồ sơ doanh nghiệp và kích hoạt ít nhất một dãy số thuộc loại fiscale, nếu không việc phát hành sẽ trả về issuer_profile_incomplete hoặc no_series_configured.
- Đổi thông tin xác thực lấy token: POST /v1/auth/token với grant_type client_credentials. Token có hiệu lực một giờ — hãy lưu tạm, đừng xin mới cho mỗi yêu cầu.
- Phát hành hóa đơn: POST /v1/invoices với header Idempotency-Key và vat_rate dưới dạng phần trăm (21, không phải 0,21).
- Chạy kiểm tra sơ bộ (CIUS-RO cho Romania) bằng POST /v1/invoices/{id}/efactura/validate, rồi gửi lên hệ thống hóa đơn điện tử quốc gia bằng POST /v1/invoices/{id}/efactura/submit.
- Theo dõi trạng thái bằng GET /v1/efactura/submissions/{id} cho đến khi đạt accepted hoặc rejected.
bash
# 1 — token (1 giờ, lưu vào bộ nhớ đệm)
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 — phát hành hóa đơn (BẮT BUỘC có 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}",
# Một khóa cho mỗi thao tác LOGIC — dùng lại khi thử lại, không bao giờ tạo mới.
"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}], # phần trăm, không phải 0,21
"currency": "RON",
}, timeout=60).json()
print(invoice["number"], invoice["totals"])Xác thực
- Luồng xác thực là client_credentials: bạn đổi client_id + client_secret lấy access_token dạng Bearer.
- Token hết hạn sau 3600 giây. Hãy làm mới trước khi hết hạn, không làm mới ở mỗi lần gọi — POST /auth/token có giới hạn tần suất theo IP riêng.
- client_secret chỉ được lưu dưới dạng băm và không thể khôi phục. Nếu làm mất, hãy tạo thông tin xác thực mới và thu hồi thông tin cũ.
- Chỉ dùng giữa máy chủ với máy chủ. Không có origin trình duyệt nào được phép qua CORS: nếu gọi từ JavaScript trên trang web, secret sẽ bị lộ cho người truy cập.
- Scope được cấp theo từng thông tin xác thực. Hãy tạo thông tin xác thực riêng cho mỗi hệ thống và chỉ cấp cho mỗi hệ thống số scope tối thiểu mà nó cần.
Lập hóa đơn → Cài đặt → Truy cậ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
// Lưu token vào bộ nhớ đệm; đừng tạo một token cho mỗi yêu cầu.
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;
}Thuế suất GTGT là phần trăm
vat_rate: 21 ✓ · vat_rate: 0.19 ✗ · vat_rate: 1 ✗- vat_rate là PHẦN TRĂM: 21 nghĩa là 21%.
- Các giá trị trong khoảng (0, 1] bị từ chối — 0,19 là một phân số, còn 1 không rõ là 1% hay 100%.
- 0 là hợp lệ và có nghĩa là miễn thuế hoặc đảo ngược nghĩa vụ thuế (reverse charge); hãy ghi căn cứ pháp lý vào notes.
- unit_price là đơn giá CHƯA THUẾ khi bên phát hành là đối tượng nộp GTGT.
- Cần tổng tiền trước khi chốt? POST /v1/invoices/preview-totals thực hiện cùng phép tính mà không sử dụng số hóa đơn tài chính.
Tính idempotent của thao tác ghi tài chính
- Header Idempotency-Key là BẮT BUỘC trên POST /invoices, POST /invoices/{id}/storno và POST /invoices/{id}/efactura/submit.
- Khóa được ghi nhớ trong 24 giờ. Phát lại cùng khóa với cùng nội dung sẽ trả về kết quả gốc và đặt Idempotency-Replayed: true.
- Cùng khóa nhưng khác nội dung sẽ trả về 409 idempotency_key_reuse — một chứng từ tài chính đã phát hành không bao giờ bị ghi đè.
- Một khóa cho mỗi thao tác LOGIC, không phải cho mỗi lần thử HTTP: order-10457-invoice, không phải một UUID mới ở mỗi lần thử lại.
- Lý do mang tính pháp lý, không phải kỹ thuật: số tài chính đã được sử dụng và hóa đơn đã nộp lên hệ thống hóa đơn điện tử quốc gia (tại Romania: SPV) không thể rút lại.
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" }
}'Mọi endpoint
| Phương thức | Endpoint | Scope | Idempotency-Key | Yêu cầu / phút |
|---|---|---|---|---|
| POST | /auth/token Đổi thông tin xác thực lấy access token | — | — | 120 |
| GET | /me Tenant, scope và giới hạn hiện tại | invoices:read | — | 120 |
| POST | /invoices Phát hành hóa đơn hoặc proforma | invoices:write | bắt buộc | 30 |
| GET | /invoices Liệt kê hóa đơn (phân trang bằng con trỏ) | invoices:read | — | 120 |
| POST | /invoices/preview-totals Tính tổng tiền mà không tạo bất cứ thứ gì | invoices:read | — | 120 |
| GET | /invoices/{invoice_id} Lấy một hóa đơn | invoices:read | — | 120 |
| GET | /invoices/{invoice_id}/pdf Tải PDF hóa đơn | invoices:read | — | 120 |
| GET | /invoices/{invoice_id}/xml Tải XML UBL (CIUS-RO cho Romania) | invoices:read | — | 120 |
| POST | /invoices/{invoice_id}/storno Phát hành hóa đơn điều chỉnh hủy hóa đơn này | invoices:write | bắt buộc | 30 |
| POST | /invoices/{invoice_id}/payments Đánh dấu hóa đơn đã thanh toán | invoices:write | — | 30 |
| DELETE | /invoices/{invoice_id}/payments Đánh dấu hóa đơn chưa thanh toán | invoices:write | — | 30 |
| GET | /clients Liệt kê khách hàng | clients:read | — | 120 |
| POST | /clients Tạo mới hoặc cập nhật khách hàng | clients:write | — | 30 |
| GET | /lookup/company/{tax_id} Xác thực CUI với sổ đăng ký ANAF (Romania) | clients:read | — | 120 |
| GET | /series Liệt kê dãy số | invoices:read | — | 120 |
| POST | /invoices/{invoice_id}/efactura/validate Kiểm tra sơ bộ (CIUS-RO cho Romania) — xác thực mà không gửi | efactura:read | — | 120 |
| POST | /invoices/{invoice_id}/efactura/submit Nộp hóa đơn lên hệ thống hóa đơn điện tử quốc gia (tại Romania: SPV) | efactura:submit | bắt buộc | 10 |
| GET | /efactura/submissions Liệt kê các lần nộp lên hệ thống hóa đơn điện tử quốc gia | efactura:read | — | 120 |
| GET | /efactura/submissions/{submission_id} Lấy một lần nộp | efactura:read | — | 120 |
| POST | /efactura/submissions/{submission_id}/refresh Hỏi hệ thống hóa đơn điện tử quốc gia về trạng thái hiện tại | efactura:read | — | 10 |
| GET | /efactura/submissions/{submission_id}/download Tải kho lưu trữ phản hồi có chữ ký | efactura:read | — | 120 |
| GET | /efactura/inbox Hóa đơn nhà cung cấp nhận qua hệ thống hóa đơn điện tử quốc gia | efactura:read | — | 120 |
| GET | /efactura/inbox/{inbound_id} Lấy một hóa đơn đến | efactura:read | — | 120 |
| GET | /efactura/inbox/{inbound_id}/download Tải kho lưu trữ hóa đơn đến | efactura:read | — | 120 |
| POST | /efactura/inbox/sync Kéo thông điệp mới từ hệ thống hóa đơn điện tử quốc gia vào hộp thư đến | 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
Chu trình hóa đơn điện tử quốc gia
- Điểm khác biệt thực sự: toàn bộ chu trình với hệ thống hóa đơn điện tử quốc gia (tại Romania: ANAF e-Factura) chạy qua API, không chỉ dừng ở việc tạo chứng từ.
- POST /invoices/{id}/efactura/validate chạy kiểm tra sơ bộ (CIUS-RO cho Romania) và trả về đúng các trường mà hệ thống hóa đơn điện tử quốc gia sẽ từ chối, mà không gửi bất cứ thứ gì.
- GET /invoices/{id}/xml tạo XML UBL (CIUS-RO cho Romania) mà không cần nộp — hữu ích cho kho lưu trữ hoặc kiểm toán nội bộ của bạn.
- POST /invoices/{id}/efactura/submit nộp hóa đơn lên hệ thống hóa đơn điện tử quốc gia (tại Romania: SPV). Thao tác này không thể hoàn tác: một bản sao thứ hai của cùng hóa đơn không thể rút khỏi hệ thống.
- Vòng đời được chuẩn hóa: not_sent → validating → queued → sent → processing → accepted | rejected | error. accepted nghĩa là đã truyền hợp lệ về mặt pháp lý; error là lỗi truyền tải phía chúng tôi và an toàn để thử lại.
- GET /efactura/submissions/{id}/download tải kho lưu trữ kèm phản hồi có chữ ký của hệ thống hóa đơn điện tử quốc gia (tại Romania: ANAF).
- Chiều nhận hóa đơn cũng có sẵn: POST /efactura/inbox/sync kéo các thông điệp mới từ hệ thống hóa đơn điện tử quốc gia (tại Romania: SPV) về và GET /efactura/inbox liệt kê các hóa đơn mà nhà cung cấp đã gửi cho bạn.
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)Lỗi
- Mọi lỗi 4xx và 5xx đều dùng cùng một khung: { "error": { type, code, message, param?, doc_url, request_id, errors? } }.
- type là loại lỗi khái quát mà bạn dùng để rẽ nhánh ("tôi có nên thử lại không?"), còn code là lý do ổn định mà bạn ánh xạ vào hệ thống của mình.
- param là đường dẫn có dấu chấm đến trường gây lỗi, ví dụ lines.0.vat_rate.
- errors[] xuất hiện khi một yêu cầu có vấn đề ở nhiều hơn một trường.
- request_id được ghi nhận phía chúng tôi. Hãy cung cấp mã này trong mọi yêu cầu hỗ trợ — nó xác định chính xác yêu cầu đó.
- 404 cũng được trả về cho một id thuộc về tenant khác: chúng tôi không bao giờ xác nhận rằng dữ liệu của người khác tồn tại.
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" }
]
}
}| Trạng thái | Loại | Mã |
|---|---|---|
| 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 |
Giới hạn tần suất
| Nhóm giới hạn | Yêu cầu / phút | Ý nghĩa |
|---|---|---|
| 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 |
- Giới hạn áp dụng theo từng thông tin xác thực, mỗi phút: 120 cho đọc, 30 cho ghi và 10 cho thao tác với hệ thống hóa đơn điện tử quốc gia (submit, refresh, inbox/sync).
- X-RateLimit-Limit, X-RateLimit-Remaining và X-RateLimit-Reset xuất hiện trên MỌI phản hồi, kể cả 429.
- 429 cũng mang Retry-After tính bằng giây. Hãy tuân thủ; đừng thử lại trong vòng lặp dày đặc.
- Nhóm 10/phút tồn tại vì tại Romania, bản thân ANAF chỉ cho phép khoảng một lệnh gọi danh sách thông điệp mỗi phút trên mỗi CIF.
- Hãy đặt /efactura/inbox/sync chạy bằng cron, không phải polling dồn dập.
- Cần mức trần cao hơn? Hãy yêu cầu qua trang Liên hệ, kèm khối lượng hằng ngày dự kiến của bạn.
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
// Tuân thủ Retry-After; không bao giờ thử lại 4xx khác 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;
}
// Khi thử lại một lần ghi PHẢI dùng CÙNG Idempotency-Key, nếu không bạn sẽ phát hành
// một chứng từ tài chính thứ hai.Hướng dẫn theo chủ đề
Mỗi chủ đề có trang riêng với các endpoint, mẫu mã và lỗi liên quan.
- Xác thực với API 4invoicesĐổi client_id và client_secret lấy token Bearer có hiệu lực một giờ, chỉ dùng giữa máy chủ với máy chủ.
- Phát hành hóa đơn qua APIPOST /invoices với Idempotency-Key, thuế GTGT theo phần trăm, cùng việc liệt kê, storno và đánh dấu đã thanh toán.
- Khách hàng và tra cứu sổ đăng ký ANAF (Romania)Thêm mới hoặc cập nhật đã loại trùng theo tax_id, kèm tra cứu sổ đăng ký ANAF (Romania) để hóa đơn điện tử không bị từ chối.
- Hóa đơn điện tử quốc gia qua APIKiểm tra sơ bộ (CIUS-RO cho Romania), nộp lên hệ thống hóa đơn điện tử quốc gia, trạng thái đã chuẩn hóa, phản hồi có chữ ký và hộp thư hóa đơn đến từ nhà cung cấp.
- Dãy số hóa đơn trong APINếu không có dãy số đang hoạt động thuộc loại fiscale, việc phát hành trả về 409 no_series_configured.
- Mã lỗi và khung lỗiMột cấu trúc trên mọi tuyến: type, code, message, param, doc_url, request_id và errors.
- Giới hạn tần suất và thử lại120 lượt đọc, 30 lượt ghi và 10 thao tác với hệ thống hóa đơn điện tử quốc gia mỗi phút trên mỗi thông tin xác thực, kèm header X-RateLimit-*.
Lỗi thường gặp
- Gửi GTGT dưới dạng phân số (0.19) hoặc dưới dạng 1. Cả hai đều bị từ chối; hãy gửi 19 hoặc 21.
- Tạo token ở mỗi yêu cầu. Token có hiệu lực một giờ; hãy lưu tạm, nếu không bạn sẽ chạm giới hạn theo IP của endpoint token.
- Tạo Idempotency-Key mới ở mỗi lần thử lại — chính kịch bản này sinh ra hóa đơn trùng lặp.
- Gọi từ JavaScript trên trình duyệt. CORS chặn mọi origin, và secret vẫn sẽ bị lộ.
- Nộp lên hệ thống hóa đơn điện tử quốc gia mà không kiểm tra sơ bộ (CIUS-RO cho Romania). Một lần nộp sai không thể rút lại.
- Polling /efactura/submissions/{id}/refresh quá 10 lần mỗi phút — nhóm này dùng chung với submit.
- Phát hành trước khi hồ sơ doanh nghiệp hoàn tất và có dãy số tài chính đang hoạt động.
Câu hỏi và trả lời cho nhà phát triển
Làm thế nào để lấy thông tin xác thực API của 4invoices?
Trong bảng điều khiển, tại Lập hóa đơn → Cài đặt → Truy cập API. Bạn nhận được client_id và client_secret; secret chỉ hiển thị một lần và chỉ được lưu dưới dạng băm.
access_token có hiệu lực trong bao lâu?
3600 giây, tức một giờ. Hãy lưu tạm và làm mới trước khi hết hạn — endpoint token có giới hạn tần suất riêng theo từng IP.
Làm thế nào để gửi thuế suất GTGT?
Dưới dạng phần trăm: 21 cho 21%. Các giá trị trong khoảng (0, 1] bị từ chối, nên cả 0,19 lẫn 1 đều không qua được kiểm tra.
Những lệnh gọi nào yêu cầu Idempotency-Key?
POST /invoices, POST /invoices/{id}/storno và POST /invoices/{id}/efactura/submit. Khóa được ghi nhớ trong 24 giờ, và khi phát lại, hệ thống trả về hóa đơn gốc kèm Idempotency-Replayed: true.
Có thể gửi hóa đơn lên hệ thống hóa đơn điện tử quốc gia trực tiếp từ API không?
Có. Chạy kiểm tra sơ bộ (CIUS-RO cho Romania) bằng /efactura/validate, nộp bằng /efactura/submit, theo dõi trạng thái đã chuẩn hóa tại /efactura/submissions/{id} và tải phản hồi có chữ ký của hệ thống (tại Romania: ANAF).
Giới hạn tần suất là bao nhiêu?
Theo từng thông tin xác thực, mỗi phút: 120 lượt đọc, 30 lượt ghi và 10 thao tác với hệ thống hóa đơn điện tử quốc gia. Mọi phản hồi đều mang các tiêu đề X-RateLimit-*, và phản hồi 429 thêm Retry-After.
Có thể gọi API từ giao diện web (frontend) của tôi không?
Không. API chỉ dành cho máy chủ-tới-máy chủ: không có origin trình duyệt nào vượt qua CORS, và client_secret không được xuất hiện trong mã mà người truy cập có thể thấy.
Vì sao tôi nhận issuer_profile_incomplete ở hóa đơn đầu tiên?
Hồ sơ doanh nghiệp chưa đầy đủ hoặc không có dãy số đang hoạt động thuộc loại fiscale. Hãy hoàn tất cả hai trong bảng điều khiển; API từ chối tạo chứng từ tài chính không hợp lệ.