Docs / Quickstart
Quickstart
From a fresh install to a business fiscalising invoices through your platform. Work against URA's sandbox until the output is right — documents submitted to production are real tax records.
What each client needs from URA
You collect these once per business you onboard:
- Their TIN, and the device number registered
against it (URA's convention is
<TIN>_02for a first web-service device). - Their PKCS#12 certificate (
.pfx) and its password. Each taxpayer has their own; they are not shared.
Nobody needs to apply for an appId. It names the
integration channel, not the integrator, and is the same value for everyone using
the web-service API. It looks like a credential, which is why it trips people up.
1. Run the platform
pip install -r requirements.txt
cp .env.example .env # set DATABASE_URL and JWT_SECRET_KEY
python init_auth.py # creates the owner account, prints the password
uvicorn api_multitenant:app --host 0.0.0.0 --port 8001
Check /health. It should report healthy with
database: connected. Tables are created on first start.
2. Log in as owner
POST /auth/login
Content-Type: application/x-www-form-urlencoded
[email protected]&password=<your password>
Returns an access_token. Send it as
Authorization: Bearer <token> on every owner endpoint.
This endpoint takes form fields, not JSON, and the field is
username rather than email. Sending JSON returns a
validation error naming fields you did not send, which is confusing the first time.
3. Onboard a business
One request creates the client, stores their certificate and issues their credentials:
POST /api/owner/add-client
Authorization: Bearer <token>
Content-Type: multipart/form-data
company_name = Acme Wholesalers Ltd
email = [email protected]
password = <their login password>
tin = 1000000000
device_no = 1000000000_02
cert_password = <their .pfx password>
test_mode = true
pfx_file = @acme.pfx
The response carries their api_key, api_secret and
api_endpoint. That is everything their ERP needs — no certificate
ever goes near their codebase.
4. The client calls your API
GET /api/external/efris/server-time
X-API-Key: efris_...
Always start with server-time. It is the lightest
call and confirms the certificate and TIN are accepted. If it succeeds and
everything else fails, the problem is the document rather than the credentials
— which separates the two most common first-day failures.
Then fiscalise:
POST /api/external/efris/submit-invoice
X-API-Key: efris_...
{
"invoice_number": "INV-2026-0042",
"customer_name": "Beta Trading Ltd",
"customer_tin": "2000000000",
"buyer_type": "0",
"items": [
{ "item_name": "Office Chair", "item_code": "CHAIR-001",
"quantity": 2, "unit_price": 150000, "tax_rate": 18 }
]
}
The response carries the fiscal document number and verification details URA assigned.
Two kinds of authentication
| Endpoints | Auth | Used by |
|---|---|---|
/api/owner/*, /api/companies/*, /auth/* |
Authorization: Bearer | You |
/api/external/efris/* |
X-API-Key | The businesses you onboard |
When URA says no
Rejections carry a numeric code. Branch on the number, never on the message text — URA rewords messages between releases and some return in mixed language.
The summary block causes most first rejections: netAmount must equal
grossAmount minus taxAmount
(1343), taxAmount must include excise
(1344) while grossAmount must exclude it
(1345), and itemCount must exclude
discount lines (1304).
Going to production
- Flip a client to
test_mode=falseonly once their sandbox output is verified correct. - Put the platform behind HTTPS — client API keys travel in a header.
- Back up the certificate store. It is not reconstructible, and losing it means re-collecting a certificate from every client.
Next
- Interface coverage — every T-code
- Error codes — what each rejection means
EFRIS Gateway handles this case already — it is one of the rejections it was calibrated against. It is a complete multi-tenant EFRIS platform, sold as source. This page stays free either way.