Developer reference
BIR EIS API documentation
How to connect an invoicing system to the BIR Electronic Invoicing System: authentication, request signing, the three endpoints, every field in the v2.01 e-invoice JSON, and the result codes EIS sends back.
This reference follows the structure of the BIR EIS API Guide and e-invoice JSON format v2.01. Endpoint paths, header names and algorithm details shown here are illustrative. Build against the official API Guide you download from the EIS Certification Portal after sign-up.
Downloads
Official EIS API guides
EIS e-invoice API Development Guide
The full API reference: authentication, HMAC signing, invoice issuance, inquiry result and callback APIs, JSON format, JWS signature, AES-256 encryption, error codes and Java code samples.
e-invoice JSON File Format v2.01
Field-by-field spec for CAS and CRM/POS invoices: field names, data types, lengths, mandatory rules, code values (DocType, TransClass, CorrectionCd) and formulas.
EIS Certification User Guide
Step-by-step screens for the EIS Certification Portal: sign-up, creating an application, generating keys, sandbox tests, requesting the EIS Certificate and the Permit to Transmit.
Documents from the EIS project team, as published on the BIR EIS Certification Portal (Downloads). Check the portal for newer versions before you build.
Overview
How an EIS integration works
An EIS integration is a server-to-server connection between your certified invoicing system (CAS, CBA or CRM/POS) and the BIR. Your system authenticates once every few hours, then sends batches of signed and encrypted invoices, then checks whether each one passed validation.
Environments
Sandbox and production
| Environment | Base URL | Who can call it | Use |
|---|---|---|---|
| Certification / sandbox | https://eis-cert.bir.gov.ph | Approved portal accounts with sandbox access | Keys, mandatory tests, certificate and PTT requests |
| Production | https://eis.bir.gov.ph | Holders of a PTT, from whitelisted IPs only | Live invoice transmission |
The mandatory sandbox tests are Authentication (2 steps), Invoice Issuance (1 step, or 3 with callback) and Inquiry Result (2 steps). You must pass all of them before requesting the EIS Certificate.
Authentication
Getting an auth token
Generate a session key
Create a random 32-byte AES-256 key in memory. It encrypts everything you send and receive until the token expires.
Encrypt the login body with the BIR public key
Put your user ID, password and the session key in a JSON body and encrypt it with RSA using the BIR public key from the portal. RSA is used for this call only.
Call the authentication endpoint
Send the encrypted body with the common headers. The response, encrypted with your session key, contains the auth token and its expiry.
Reuse the token, refresh before expiry
Send the token on every call for up to 6 hours. Request a new one shortly before it expires, or set
forceRefreshTokento replace an active token.
Get an auth token
// Headers: accreditationId, applicationId, datetime, Authorization (HMAC) { "data": "base64( RSA_OAEP(BIR_PUBLIC_KEY, json_body) )" }
{
"userId": "your-portal-user",
"password": "••••••••",
"sessionKey": "base64(random 32-byte AES key)",
"forceRefreshToken": false
}
{
"status": 1,
"data": "AES256(sessionKey, { authToken, tokenExpiry })"
}
// decrypted → { "authToken": "eyJhbGciOi...", "tokenExpiry": "2026-10-07 18:30:00" }
Request headers
Headers and the HMAC signature
| Header | Description | |
|---|---|---|
accreditationId | required | Your EIS certification / accreditation identifier |
applicationId | required | ID of the application you created on the Certification Portal |
datetime | required | Current timestamp, within ±10 minutes of BIR server time |
Authorization | required | HMAC-SHA256 signature of the request, keyed with your Application Key |
authToken | after auth | Token from the authentication call, valid for 6 hours |
Content-Type | required | application/json |
The HMAC proves the request came from your application and was not altered on the way. Your system builds a string from request values (such as the IDs, the datetime and the body), signs it with the Application Key, and puts the result in Authorization. The exact string-to-sign is defined in the API Guide.
Servers with a drifting clock get every request refused. Sync your transmitting servers with NTP and send the datetime header in the format the API Guide specifies.
Endpoints
Submitting invoices and reading results
Submit e-invoices
Send 1 to 100 signed invoices per call. submitId identifies the batch on your side and must be unique. EIS accepts the batch, returns a reference number, and validates each invoice on its own.
// Headers: accreditationId, applicationId, datetime, Authorization, authToken { "submitId": "SUB-20261007-000123", "data": "AES256(sessionKey, [ JWS(invoice_1), JWS(invoice_2), ... ])" }
{
"status": 1,
"data": "AES256(sessionKey, { refNo, submitId, receivedDtm })"
}
// store refNo with the batch: you need it to look up results
Check validation results
Look up a submission by reference number. Each invoice in the batch returns its own result code.
{
"data": "AES256(sessionKey, { \"refNo\": \"EIS2026100700045821\" })"
}
{
"refNo": "EIS2026100700045821",
"results": [
{ "eisUniqueId": "20261007A1B2C3D40000012F", "code": "SUC001" },
{ "eisUniqueId": "20261007A1B2C3D400000130", "code": "ERR004",
"message": "VAT must be 0.00 for exempt or zero-rated items" }
]
}
Result callback (optional)
If you register a callback URL, EIS pushes the validation result to your server instead of you polling. The Invoice Issuance sandbox test then has 3 steps instead of 1. Your endpoint must be reachable over HTTPS and should acknowledge quickly, then process the result.
Request sequence
End-to-end request flow
sequenceDiagram
participant S as Your system
participant E as BIR EIS
S->>E: Authenticate (RSA-encrypted session key)
E-->>S: authToken (valid 6h)
S->>S: Invoice to JSON, sign (JWS), encrypt (AES-256)
S->>E: Submit 1-100 invoices + HMAC header
E-->>S: Accepted (refNo)
S->>E: Inquire result (refNo)
E-->>S: Per-invoice code (SUC001 / SYN / ERR)
e-Invoice JSON v2.01
Invoice field reference
The same structure serves CAS and CRM/POS invoices; POS invoices also carry the PTU number. When a field doesn't apply, strings take null or blank and numbers take 0.00.
EisUniqueId (24 characters)
The date part must match IssueDtm (else ERR002). Reusing an ID returns SYN003. Build or decode one →
Document
| Field | Type | Description |
|---|---|---|
CompInvoiceId | string | Your own invoice number as printed on the invoice |
IssueDtm | date | Issue date. Cannot be later than transmission; must match the EisUniqueId date |
EisUniqueId | string(24) | Unique EIS identifier for this invoice |
DocType | code | Sales Invoice, Debit Memo, Credit Memo, Service Billing or Official Receipt |
TransClass | code | VATable, Zero-Rated or VAT Exempt. Mixed classes need separate invoices |
CorrYN | Y / N | Whether this invoice corrects a previous one |
CorrectionCd | code | Type of correction, when CorrYN is Y |
PrevUniqueId | string(24) | EisUniqueId of the invoice being corrected |
Rmk1 | string | Remarks |
PtuNum | string | Permit to Use number (CRM/POS invoices) |
SellerInfo
| Field | Type | Description |
|---|---|---|
Tin | 9 digits | Seller TIN without dashes or branch code, e.g. 123456789 |
BranchCd | 5 digits | Issuing branch. 00000 is the head office |
Type | code | VAT registration type (VAT or non-VAT) |
RegNm | string | Registered name per BIR Form 2303 |
BusinessNm | string | Trade or business name |
Email | string | Seller email |
RegAddr | string | Registered address |
BuyerInfo
| Field | Type | Description |
|---|---|---|
Tin · BranchCd | 9 · 5 digits | Buyer TIN and branch. Required for B2B; null convention when the buyer has none |
RegNm · BusinessNm | string | Buyer registered and business names |
Email · RegAddr | string | Buyer email and registered address |
DevAddr | string | Delivery address |
AirNum · AirNumDt | string · date | Air waybill number and date (shipped goods) |
LadNum · LadNumDt | string · date | Bill of lading number and date (shipped goods) |
ItemList (1 to 1,000 items)
| Field | Type | Description |
|---|---|---|
Nm · Desc | string | Item name and description |
Qty · Unit | number · string | Quantity and unit of measure |
UnitCost | number | Unit price, net of VAT for VATable items |
SalesAmt | number | Qty × UnitCost, net of VAT |
RegDscntAmt | number | Regular discount on the item |
SpeDscntAmt | number | Special discount on the item (e.g. SC/PWD) |
NetSales | number | SalesAmt − RegDscntAmt − SpeDscntAmt |
Totals, discounts and taxes
| Field | Type | Description |
|---|---|---|
TotNetItemSales | number | Sum of item NetSales |
Discount.ScAmt · PwdAmt | number | Senior citizen and PWD discounts |
Discount.RegAmt · SpeAmt | number | Regular and other special discounts |
Discount.Rmk2 | string | Discount remarks |
OtherTaxRev | number | Other taxable revenue |
TotNetSalesAftDisct | number | Net sales after discounts; the VAT base |
VATAmt | number | 12% of the VAT base for VATable sales; 0.00 for zero-rated or exempt |
WithholdIncome | number | Creditable withholding income tax deducted by the buyer |
WithholdBusVAT · WithholdBusPT | number | Withheld business VAT and percentage tax |
OtherNonTaxCharge | number | Non-taxable charges |
NetAmtPay | number | Amount payable after VAT, withholding and other charges |
ForCur.Currency · ConvRate · ForexAmt | string · number | Foreign currency, conversion rate and foreign amount |
Example
Sample CAS invoice
A VATable B2B sale with two items and 1% creditable withholding. Code values are illustrative. Load it into the validator to see each check pass.
{
"CompInvoiceId": "SI-000123",
"IssueDtm": "20261007",
"EisUniqueId": "20261007A1B2C3D40000012F",
"DocType": "SI",
"TransClass": "VT",
"CorrYN": "N", "CorrectionCd": null, "PrevUniqueId": null, "Rmk1": "",
"SellerInfo": {
"Tin": "123456789", "BranchCd": "00000", "Type": "V",
"RegNm": "Sample Trading Corp.", "BusinessNm": "Sample Trading",
"Email": "billing@sample.ph", "RegAddr": "123 Ayala Ave, Makati City"
},
"BuyerInfo": {
"Tin": "987654321", "BranchCd": "00000",
"RegNm": "Example Retail Inc.", "BusinessNm": "Example Retail",
"Email": "ap@example.ph", "RegAddr": "45 Ortigas Ave, Pasig City", "DevAddr": "",
"AirNum": "", "AirNumDt": "", "LadNum": "", "LadNumDt": ""
},
"ItemList": [
{ "Nm": "Thermal paper roll 80mm", "Desc": "Box of 50", "Qty": 100, "Unit": "box",
"UnitCost": 25.00, "SalesAmt": 2500.00, "RegDscntAmt": 0.00, "SpeDscntAmt": 0.00, "NetSales": 2500.00 },
{ "Nm": "Receipt printer", "Desc": "USB/LAN", "Qty": 2, "Unit": "pc",
"UnitCost": 4750.00, "SalesAmt": 9500.00, "RegDscntAmt": 0.00, "SpeDscntAmt": 0.00, "NetSales": 9500.00 }
],
"TotNetItemSales": 12000.00,
"Discount": { "ScAmt": 0.00, "PwdAmt": 0.00, "RegAmt": 0.00, "SpeAmt": 0.00, "Rmk2": "" },
"OtherTaxRev": 0.00,
"TotNetSalesAftDisct": 12000.00,
"VATAmt": 1440.00,
"WithholdIncome": 120.00, "WithholdBusVAT": 0.00, "WithholdBusPT": 0.00,
"OtherNonTaxCharge": 0.00,
"NetAmtPay": 13320.00,
"ForCur": { "Currency": "PHP", "ConvRate": 1.00, "ForexAmt": 0.00 },
"PtuNum": ""
}
Security
Signing and encryption in code
// npm i jose · Algorithms, AES mode and the HMAC string-to-sign are placeholders: // take the exact values from the official EIS API Guide. import { CompactSign, importPKCS8 } from 'jose'; import crypto from 'node:crypto'; // 1) Login body, RSA-encrypted with the BIR public key (auth call only) const sessionKey = crypto.randomBytes(32); const loginData = crypto.publicEncrypt( { key: BIR_PUBLIC_KEY_PEM, padding: crypto.constants.RSA_PKCS1_OAEP_PADDING, oaepHash: 'sha256' }, Buffer.from(JSON.stringify({ userId, password, sessionKey: sessionKey.toString('base64'), forceRefreshToken: false })) ).toString('base64'); // 2) Sign each invoice as a JWS with your private signing key const signingKey = await importPKCS8(process.env.EIS_SIGNING_KEY, 'RS256'); const jws = await new CompactSign(new TextEncoder().encode(JSON.stringify(invoice))) .setProtectedHeader({ alg: 'RS256' }) .sign(signingKey); // 3) Encrypt the batch with the session key function aesEncrypt(key, text) { const iv = crypto.randomBytes(16); const c = crypto.createCipheriv('aes-256-cbc', key, iv); return Buffer.concat([iv, c.update(text, 'utf8'), c.final()]).toString('base64'); } const body = { submitId, data: aesEncrypt(sessionKey, JSON.stringify([jws])) }; // 4) HMAC-SHA256 Authorization header, keyed with the Application Key const datetime = new Date().toISOString(); const toSign = [accreditationId, applicationId, datetime, JSON.stringify(body)].join(''); const authorization = crypto.createHmac('sha256', APP_KEY).update(toSign).digest('base64');
# pip install python-jose cryptography · placeholders: follow the official API Guide import os, json, base64, hmac, hashlib, datetime from jose import jws from cryptography.hazmat.primitives import hashes, padding as sympad, serialization from cryptography.hazmat.primitives.asymmetric import padding from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes session_key = os.urandom(32) # 1) Login body, RSA-OAEP with the BIR public key bir_pub = serialization.load_pem_public_key(BIR_PUBLIC_KEY_PEM) login = json.dumps({"userId": USER, "password": PWD, "sessionKey": base64.b64encode(session_key).decode(), "forceRefreshToken": False}) login_data = base64.b64encode(bir_pub.encrypt(login.encode(), padding.OAEP(mgf=padding.MGF1(hashes.SHA256()), algorithm=hashes.SHA256(), label=None))).decode() # 2) JWS signature signed = jws.sign(invoice, os.environ["EIS_SIGNING_KEY"], algorithm="RS256") # 3) AES-256 encryption def aes_encrypt(key, text): iv = os.urandom(16) p = sympad.PKCS7(128).padder(); data = p.update(text.encode()) + p.finalize() enc = Cipher(algorithms.AES(key), modes.CBC(iv)).encryptor() return base64.b64encode(iv + enc.update(data) + enc.finalize()).decode() body = {"submitId": SUBMIT_ID, "data": aes_encrypt(session_key, json.dumps([signed]))} # 4) HMAC-SHA256 Authorization header dt = datetime.datetime.now(datetime.timezone.utc).isoformat() to_sign = ACCREDITATION_ID + APPLICATION_ID + dt + json.dumps(body) authorization = base64.b64encode(hmac.new(APP_KEY, to_sign.encode(), hashlib.sha256).digest()).decode()
The private signing key is shown once on the portal. Keep it in a secrets manager or HSM, never in source control, and never paste a production key into a web tool. Use sandbox keys for testing.
Troubleshooting
EIS API response codes
| Code | Status | Meaning | What to fix |
|---|---|---|---|
SUC001 | Accepted | All validation checks passed | Nothing. Store the result with the invoice |
SYN002 | Rejected | Invalid digital signature | Check the JWS algorithm and signing key, and that the payload wasn't changed after signing |
SYN003 | Rejected | Duplicate EisUniqueId | Generate a new control value; never resend an accepted ID |
SYN004 | Rejected | Schema error | Missing or extra fields, wrong data type or format |
ERR001 | Rejected | Seller TIN not registered | Confirm the 9-digit TIN and branch code match BIR records |
ERR002 | Rejected | Invalid issuance datetime | Date is later than transmission or doesn't match the EisUniqueId date |
ERR004 | Rejected | Total sales / VAT error | Re-check totals; VAT must be 0.00 for exempt or zero-rated items |
Before go-live
Go-live checklist
- All sandbox tests passed
Authentication, Invoice Issuance and Inquiry Result, on the Certification Portal.
- EIS Certificate and PTT approved
EIS Certification Number issued and Permit to Transmit approved by email.
- Production IPs whitelisted
Fixed public IPs of every transmitting server registered on the portal.
- Keys stored securely
Signing private key and Application Key in a secrets manager, with rotation documented.
- Servers synced to NTP
Clock drift stays well inside ±10 minutes.
- Token refresh automated
New token requested before the 6-hour expiry.
- Retry and resubmission flow
Rejected invoices are fixed and resent within the 3-day window; network failures retry without creating duplicate EisUniqueIds.
- Results stored for 10 years
Invoice JSON, JWS, BIR reference numbers and result codes kept with the audit trail.