# TConnect Open API — Tra cứu hóa đơn đầu vào (single-file cho AI Agent) FILE NÀY LÀ ĐẶC TẢ ĐẦY ĐỦ — AI Agent có thể TÍCH HỢP TRỰC TIẾP từ đây, không cần đọc tài liệu web. Cần: (1) Base URL, (2) token Bearer (login Keycloak) + Partner-Code, rồi theo "Luồng cơ bản" + "ENDPOINTS" bên dưới. Bộ API cho phép tra cứu hóa đơn mua vào (đầu vào) của doanh nghiệp từ cổng thuế (Tổng cục Thuế). Đối tác cung cấp tài khoản cổng thuế (MST + mật khẩu); hệ thống tự đăng nhập, giải captcha, đồng bộ hóa đơn (HĐĐT + máy tính tiền), lưu mã hóa và trả về qua API. ## An toàn & bảo mật - Chỉ lấy hộ, KHÔNG can thiệp: hệ thống tự động lấy đúng hóa đơn của bạn từ cổng thuế thay cho thao tác thủ công; KHÔNG sửa, KHÔNG xử lý, KHÔNG thay đổi dữ liệu gốc — giữ nguyên như trên cổng thuế. - Mã hóa mạnh: MST băm HMAC-SHA256 (không lưu thô); mật khẩu cổng thuế + dữ liệu nhạy cảm (tên, số tiền, dòng hàng) mã hóa AES-256-GCM. ## Base URL - Staging: https://sme-open-api-sandbox.tconnect.vn/openapi/v1/api-invoice - Production: Được cung cấp sau khi hoàn tất kiểm thử (Chỉ test trên Staging.) ## Xác thực (JWT Bearer — Keycloak, dùng chung với API Thanh toán) 1) Lấy token: POST {AUTH_BASE}/openapi/v1/auth/login - Header: Partner-Code: - Body: {"data": AES-256-CBC_hex({username,password,client_id,client_secret}, aes_key)} (AES-256-CBC, IV 16B nối đầu ciphertext, PKCS7, hex. TConnect cấp: partner_code, client_id, client_secret, aes_key.) - Response: { access_token, refresh_token, expires_in, token_type:"Bearer", ... } (Keycloak realm 'tconnect') 2) Mọi request Invoice API thêm header: Authorization: Bearer Partner-Code: 3) Hết hạn: POST {AUTH_BASE}/openapi/v1/auth/refresh-token để lấy access_token mới. Keycloak: issuer https://account-sandbox.tconnect.vn/realms/tconnect (sandbox) · JWKS .../protocol/openid-connect/certs (RS256). ## Quy ước - Định danh (accountId, jobId, invoiceId) là UUID. - Ngày: YYYY-MM-DD theo giờ Việt Nam (UTC+7). - Cổng thuế giới hạn mỗi truy vấn ~31 ngày; khoảng dài hơn được tự chia thành nhiều cửa sổ. - Lỗi HTTP: 400 (dữ liệu sai/UUID sai), 404 (không tồn tại), 401/403 (xác thực/không có quyền). - Bảo mật lưu trữ: MST băm HMAC-SHA256; mật khẩu + tên/số tiền/dòng hàng mã hóa AES-256-GCM. Cột lọc plaintext: sellerTaxCode, buyerTaxCode, invoiceSymbol, invoiceNumber, issueDate. ## Luồng cơ bản 1) POST /tax-accounts → accountId 2) POST /tax-accounts/{id}/sync → jobId (async) 3) GET /sync-jobs/{jobId} → poll đến status=succeeded 4) GET /invoices?accountId=... → danh sách 5) GET /invoices/{invoiceId} → chi tiết + dòng hàng =========================================================================== ENDPOINTS =========================================================================== ### 1. Đăng ký tài khoản cổng thuế POST /tax-accounts Body: mst (string, bắt buộc), password (string, bắt buộc), triggerInitialSync (bool, default true), initialSync { dateFrom, dateTo, fetchDetails } Response 201: { id, maskedMst, status:"active", initialJob:{id,status} } Ghi chú: id = accountId. Quản lý: GET /tax-accounts, GET/PATCH/DELETE /tax-accounts/{accountId}. PATCH cho phép đổi password (tự gỡ khóa nếu bị INVALID_CREDENTIALS). ### 2. Yêu cầu đồng bộ hóa đơn POST /tax-accounts/{accountId}/sync Body: dateFrom (date, default 30 ngày trước), dateTo (date, default hôm nay), direction (string, default "purchase"), fetchDetails (bool, default true; false = chỉ header, rẻ hơn) Response 202: { id (jobId), status:"queued", priority } ### 3. Theo dõi tiến trình GET /sync-jobs/{jobId} Response: { id, status, totalFound, totalSaved, errorCode, errorMessage } status: queued | running | succeeded | failed errorCode: INVALID_CREDENTIALS (sai mật khẩu → cần cập nhật mật khẩu) | SYNC_FAILED Danh sách: GET /sync-jobs?accountId=&status=&page=&limit= ### 4. Danh sách hóa đơn GET /invoices?accountId={uuid}&direction=purchase&sellerTaxCode=&dateFrom=&dateTo=&page=1&limit=20&sort=issueDate:desc Response: { data:[ {id, sellerTaxCode, sellerName, buyerTaxCode, invoiceSymbol, invoiceNumber, issueDate, totalBeforeTax, totalTax, totalAmount, currency} ], total, page, limit, totalPage } Lưu ý: sort chỉ theo issueDate|syncedAt (tiền được mã hóa, không sort theo tiền). ### 5. Chi tiết hóa đơn GET /invoices/{invoiceId} Response: { id, invoiceSymbol, invoiceNumber, issueDate, sellerName, sellerTaxCode, sellerAddress, buyerName, buyerTaxCode, paymentMethod, totalBeforeTax, totalTax, totalAmount, totalInWords, currency, mccqt, lookupCode, taxBreakdown:[{taxRate, amountBeforeTax, taxAmount}], items:[{lineNumber, description, unit, quantity, unitPrice, amount, taxRate, taxAmount, totalAmount}] } items chỉ có khi đồng bộ với fetchDetails=true hoặc sau khi gọi fetch-detail. ### 6. Lấy chi tiết on-demand POST /invoices/{invoiceId}/fetch-detail Response 202: { jobId, status:"queued", invoiceId } Tạo yêu cầu ưu tiên cao lấy chi tiết đúng 1 hóa đơn. Sau khi succeeded, gọi lại GET /invoices/{invoiceId} để nhận items. ### 7. Đối soát sử dụng GET /tax-accounts/{accountId}/usage?month=YYYY-MM Response: { accountId, month, invoices:{ jobs, syncJobs, detailJobs, windows, invoicesNew, billableInvoices }, requests:{ total, byEndpoint:[{method, route, count}] } } =========================================================================== CURL MẪU =========================================================================== # Đăng ký curl -X POST "$BASE/tax-accounts" -H "Authorization: Bearer $TOKEN" -H "Partner-Code: $PC" -H "Content-Type: application/json" \ -d '{"mst":"0319074775","password":"...","initialSync":{"dateFrom":"2025-03-01","dateTo":"2025-03-31"}}' # Đồng bộ curl -X POST "$BASE/tax-accounts/$ACC/sync" -H "Authorization: Bearer $TOKEN" -H "Partner-Code: $PC" -H "Content-Type: application/json" \ -d '{"dateFrom":"2025-03-01","dateTo":"2025-03-31","fetchDetails":true}' # Poll curl "$BASE/sync-jobs/$JOB" -H "Authorization: Bearer $TOKEN" -H "Partner-Code: $PC" # Danh sách curl "$BASE/invoices?accountId=$ACC&direction=purchase" -H "Authorization: Bearer $TOKEN" -H "Partner-Code: $PC" # Chi tiết curl "$BASE/invoices/$INV" -H "Authorization: Bearer $TOKEN" -H "Partner-Code: $PC"