API e-Billing Sipajak menerbitkan ID Billing (kode billing) pajak langsung dari sistem kamu: validasi NPWP dan kode KAP/KJS lewat inquiry, lalu buat kode pembayaran yang siap dibayar di bank persepsi.
| Method | Path | Fungsi |
|---|---|---|
| POST | /v2/ebilling/inquiry | Validate NPWP + KAP/KJS and fetch taxpayer name and address |
| POST | /v2/ebilling/create | Generate a DJP payment code (ID Billing) |
The e-Billing module issues DJP payment codes (ID Billing) — the 15-digit references that taxpayers quote at banks, ATMs, and payment gateways to settle a tax liability. The flow has two endpoints: inquiry validates the taxpayer and the tax-account combination, and create issues the actual billing code.
Both endpoints proxy to DJP's CTAS billing service. Unlike VSWP — the other DJP-passthrough module — e-Billing wraps DJP business errors as HTTP 400 rather than returning them inside an HTTP 200 envelope. This is a deliberate convention break, documented in detail below.
Convention break — soft errors return HTTP 400. Section 3.2 documents Sipajak's soft-error pattern: DJP business failures are surfaced as HTTP 200 with data.status="0". The e-Billing module does NOT follow this convention. e-Billing converts DJP soft errors (data.status=0) into NestJS BadRequestException, which surfaces to the client as HTTP 400. The original DJP envelope is preserved inside the wrapped error body. See section 9.3 for the exact shape. Plan your error-handling code accordingly: for e-Billing, branch on HTTP status first (200 vs 400), then inspect the body. For all other modules covered so far, branch on data.status inside an HTTP 200 envelope.
9.1 Endpoint summary
| Method | Path | Purpose |
|---|---|---|
| POST | /v2/ebilling/inquiry | Validate NPWP + KAP/KJS and fetch taxpayer name and address |
| POST | /v2/ebilling/create | Generate a DJP payment code (ID Billing) |
9.2 Module conventions
Four field-level conventions apply across both e-Billing endpoints. Read this section before either endpoint reference.
9.2.1 The traceId field
Every e-Billing request carries a traceId in the body. Sipajak generates this value server-side — callers do not supply it. Its purpose differs between the two endpoints.
| Endpoint | Generation | Idempotency role |
|---|---|---|
| POST /v2/ebilling/inquiry | Fresh Unix epoch in milliseconds, generated per call | None. Re-inquiring with a new traceId returns the same DJP data. |
| POST /v2/ebilling/create | UUIDv4 by default, generated per call | Idempotency key. See below. |
On create, Sipajak uses traceId as an idempotency key with a one-hour TTL. The cache key is sha256(organisation + bankID + branchID + amount + KAP + KJS + masa1 + masa2 + tahun + noSK + nop). Behaviour:
- First attempt for a given request shape: a fresh UUIDv4 is generated, the call goes to DJP, and the traceId is cached for one hour against the request-shape hash.
- Retry within one hour with the same request shape: the cached traceId is reused. DJP treats this as the same logical billing request and returns the same idBilling, even if its internal state machine had paused on the first call.
- On a successful create (DJP data.status = 1), the cache entry is deleted so subsequent identical requests start fresh. Callers that want explicit idempotency control can supply an external_id in the upstream request envelope. When present, Sipajak uses external_id verbatim as the traceId and bypasses the request-shape hash.
9.2.2 The periode field
Important. The periode format on e-Billing is NOT the same as e-Faktur's tanggalFaktur. It is an eight-character concatenation of masa1, masa2, and tahun, in that order. masa1 and masa2 are zero-padded two-digit month numbers; tahun is the four-digit year.
Examples:
| periode | masa1 | masa2 | tahun | Meaning |
|---|---|---|---|---|
| 01122026 | 01 | 12 | 2026 | Tax period spanning January through December 2026 |
| 12122024 | 12 | 12 | 2024 | December 2024 only |
| 01062025 | 01 | 06 | 2025 | January through June 2025 |
DJP validates the field length first ("Periode tidak 8 digit") and the KAP-KJS-period combination second ("Periode tidak sesuai dengan validasi kombinasi KAP-KJS"). The second message is overloaded — it covers both invalid combinations and out-of-range periods for the chosen KAP-KJS.
9.2.3 The requestDate field
requestDate is server-generated and uses the DJP-mandated format YYYY-MM-DD HH:mm:ss. The clock is the Sipajak service's local time, which runs in Asia/Jakarta. ISO 8601 with timezone designators is not accepted by DJP — clients calling the upstream Sipajak module do not need to think about this field at all.
9.2.4 The confirm field
DJP supports a two-step confirmation flow on create. The confirm field controls which mode is used. It is always a string.
| confirm | Mode | Behaviour |
|---|---|---|
| "0" (default) | Single-step | DJP issues idBilling immediately on the first call. This is the path the great majority of integrations should use. |
| "1" | Two-step | First call returns a draft response. The client must re-submit with confirm="1" AND the same traceId. Submitting with a different traceId is rejected by DJP. |
9.3 Inquiry
POST /v2/ebilling/inquiry
Validates a (NPWP, KAP, KJS) tuple at DJP and returns the taxpayer's master-data record (name, address) plus the human-readable description of the tax account and sub-account codes. Call this before create to confirm the combination is acceptable to DJP.
Request headers
| Header | Required | Description |
|---|---|---|
Authorization | Yes | HTTP Basic Auth (see section 2.2) |
npwp | Yes | Organisation NPWP (see section 2.3) |
Content-Type | Yes | application/json |
Request body
| Field | Type | Required | Description |
|---|---|---|---|
npwp | string | Yes | The taxpayer NPWP being looked up (15 or 16 digits). May or may not match the npwp header — clients may inquire on behalf of partner NPWPs. |
bankID | string | Yes | DJP bank identifier (4 digits). Identifies which DJP-affiliated payment partner this billing will flow through. |
branchID | string | Yes | DJP branch identifier (6 digits) corresponding to the chosen bankID. |
kdMAP | string | Yes | DJP tax-account code (Kode Akun Pajak). Six digits. |
kjs | string | Yes | DJP tax sub-account code (Kode Jenis Setoran). Three digits. |
authCode | string | No | DJP authentication token when required by the bank/branch combination. Sipajak forwards this verbatim to DJP. |
requestDate | string | No (server-generated) | If absent, Sipajak generates the current Asia/Jakarta timestamp. See section 9.2.3. |
traceId | string | No (server-generated) | If absent, Sipajak generates a fresh epoch-ms value. |
Example request
POST /v2/ebilling/inquiry HTTP/1.1
Host: gateway-integration.sipajak.com
Authorization: Basic <base64-encoded credentials>
npwp: 1234567890123000
Content-Type: application/json
{
"requestDate": "2026-01-22 17:36:38",
"bankID": "0923",
"branchID": "000001",
"authCode": "<djp-auth-code>",
"npwp": "2345678901234000",
"kdMAP": "411618",
"kjs": "100",
"traceId": "1769078198260"
}Example response — success
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
{
"status_code": 200,
"message": "OK",
"data": {
"requestDate": "2026-01-22 17:36:38",
"bankID": "0923",
"branchID": "000001",
"npwp": "2345678901234000",
"namaWP": "PT Contoh Sejahtera",
"alamatWP": "Jalan Contoh No. 1, Jakarta 12345",
"namaKdMap": "Pendapatan Pajak Tidak Langsung Lainnya Deposit",
"namaKjs": "Pembayaran Masa",
"traceId": "1769078198260",
"status": 1,
"message": "Sukses",
"uuid": "73896e3b-bb08-4e82-a234-4e6dac509b8b"
}
}Example response — taxpayer not found
When DJP cannot resolve the NPWP or the (NPWP, KAP, KJS) combination, the response is HTTP 400 with the DJP envelope preserved inside a NestJS-style error wrapper.
HTTP/1.1 400 Bad Request
Content-Type: application/json; charset=utf-8
{
"response": {
"requestDate": "2026-01-22 17:36:38",
"bankID": "0923",
"branchID": "000001",
"npwp": "2345678901234000",
"namaWP": "PT Contoh Sejahtera",
"alamatWP": "Jalan Contoh No. 1, Jakarta 12345",
"namaKdMap": null,
"namaKjs": "PPh Final UMKM Setor Sendiri",
"traceId": "1769078198260",
"status": 0,
"message": "NPWP tidak 16 digit, termasuk NPWP tidak ada dalam database",
"uuid": "aaa5f5f4-11f1-4a40-b0c3-30e56b95213a",
"correlation_id": "88787729-f750-4afb-a201-744960a6343a",
"status_code": 400
},
"status": 400,
"options": {},
"message": "NPWP tidak 16 digit, termasuk NPWP tidak ada dalam database",
"name": "BadRequestException",
"correlation_id": "88787729-f750-4afb-a201-744960a6343a"
}Note that even on the not-found path, DJP may still populate namaWP, alamatWP, and namaKjs from master data when those records exist but the combination is rejected. The status field at body.response.status is 0 (the DJP soft-error signal that was converted to HTTP 400), and the human-readable reason is at body.response.message.
Response data fields — success
| Field | Type | Description |
|---|---|---|
namaWP | string | Taxpayer name from DJP master data |
alamatWP | string | Registered address |
namaKdMap | string | Human-readable description of the kdMAP (tax account) |
namaKjs | string | Human-readable description of the kjs (sub-account) |
status | integer | 1 on success |
message | string | "Sukses" on success |
uuid | string | Per-call DJP correlation ID, distinct from traceId |
9.4 Create
POST /v2/ebilling/create
Issues a DJP payment code (idBilling) for a given tax-payable amount and KAP/KJS combination. The returned idBilling is a 15-digit reference that the taxpayer quotes at the bank to settle the obligation.
Idempotency. Re-submitting an identical request body within one hour returns the same idBilling — Sipajak caches the upstream traceId keyed on the request-shape hash. See section 9.2.1 for the cache key composition. To force a fresh attempt, vary any field in the request body.
Request body
| Field | Type | Required | Description |
|---|---|---|---|
npwp | string | Yes | Taxpayer NPWP being billed |
bankID | string | Yes | DJP bank identifier |
branchID | string | Yes | DJP branch identifier |
currencyCode | string | Yes | ISO 4217 currency code. Currently DJP accepts IDR. |
billingAmount | integer | Yes | Total payment amount in the smallest currency unit (rupiah, no decimals). Must equal the sum of detail[].numAccount. |
numRecordDetail | integer | Yes | Always 1 in current API. Multi-record billings are not supported via this gateway. |
uraian | string | Yes | Free-text description visible on payment receipts. Treat this as a payment memo line. |
confirm | string | No | "0" (default) for single-step, "1" for two-step. See section 9.2.4. |
detail | array | Yes | Exactly one element — see Detail fields below. |
authCode | string | No | DJP authentication token when required |
requestDate | string | No (server-generated) | See section 9.2.3 |
traceId | string | No (server-generated) | See section 9.2.1 |
Detail fields
Exactly one element. Fields below describe the shape of detail[0].
| Field | Type | Required | Description |
|---|---|---|---|
kdMAP | string | Yes | DJP tax-account code (six digits). Note: returned as integer in the response. |
kjs | string | Yes | DJP tax sub-account code (three digits) |
periode | string | Yes | Eight-character MMMMYYYY. See section 9.2.2. |
numAccount | integer | Yes | Amount allocated to this detail. Must equal billingAmount when numRecordDetail = 1. |
traceId | string | Yes | Must equal the top-level traceId on the create request. |
noSK | string (nullable) | Conditional | DJP decision-letter reference (Nomor Surat Keputusan). Required for certain KAP/KJS combinations (e.g. penalty payments after a tax audit). |
nop | string | Conditional | DJP tax-object reference (Nomor Objek Pajak). Required for property-tax KAP/KJS combinations; empty string otherwise. |
taxObjectAddress | string | Conditional | Address of the tax object. Pairs with nop. |
kelurahan | string | Conditional | Sub-district of the tax object |
kecamatan | string | Conditional | District |
kabKota | string | Conditional | Regency or city |
provinsi | string | Conditional | Province |
Example request
POST /v2/ebilling/create HTTP/1.1
Host: gateway-integration.sipajak.com
Authorization: Basic <base64-encoded credentials>
npwp: 1234567890123000
Content-Type: application/json
{
"requestDate": "2026-01-22 17:36:59",
"bankID": "0923",
"branchID": "000001",
"authCode": "<djp-auth-code>",
"npwp": "2345678901234000",
"currencyCode": "IDR",
"billingAmount": 250000000,
"numRecordDetail": 1,
"uraian": "PPh Final UMKM Desember 2026",
"confirm": "0",
"traceId": "3ecd36ab-0d70-46c4-94cc-85ab8f22a2f0",
"detail": [
{
"kdMAP": "411618",
"kjs": "100",
"periode": "01122026",
"noSK": null,
"nop": "",
"taxObjectAddress": "",
"kelurahan": "",
"kecamatan": "",
"kabKota": "",
"provinsi": "",
"numAccount": 250000000,
"traceId": "3ecd36ab-0d70-46c4-94cc-85ab8f22a2f0"
}
]
}Example response — success
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
{
"status_code": 200,
"message": "OK",
"data": {
"requestDate": "2026-01-22 17:36:59",
"bankID": "0923",
"branchID": "000001",
"npwp": "2345678901234000",
"namaWP": "PT Contoh Sejahtera",
"alamatWP": "Jalan Contoh No. 1, Jakarta 12345",
"currencyCode": "IDR",
"billingAmount": 250000000,
"numRecordDetail": 1,
"uraian": "PPh Final UMKM Desember 2026",
"traceId": "3ecd36ab-0d70-46c4-94cc-85ab8f22a2f0",
"idBilling": "123456789012345",
"tglKadaluarsa": "2026-02-05 17:37:00",
"billingCreationDate": "2026-01-22 17:37:00",
"type": "0",
"detail": [
{
"kdMAP": 411618,
"kjs": "100",
"periode": "01122026",
"noSK": null,
"nop": "",
"taxObjectAddress": "",
"kelurahan": "",
"kecamatan": "",
"kabKota": "",
"provinsi": "",
"numAccount": 250000000,
"traceId": "3ecd36ab-0d70-46c4-94cc-85ab8f22a2f0"
}
],
"status": 1,
"message": "Sukses",
"uuid": "f2fbc0c2-e917-40db-b93c-b71d50cdaa92"
}
}Response data fields — success
| Field | Type | Description |
|---|---|---|
idBilling | string | The 15-digit DJP payment reference. This is the value the taxpayer quotes at the bank or payment gateway. |
tglKadaluarsa | string (YYYY-MM-DD HH:mm:ss) | DJP expiry timestamp for this idBilling, in Asia/Jakarta. Typically 14 days from creation. Payment after expiry is rejected. |
billingCreationDate | string (YYYY-MM-DD HH:mm:ss) | DJP-side creation timestamp, distinct from the client-supplied requestDate |
type | string | DJP-defined billing type code. Observed value: "0". Meaning is documented in DJP's CTAS specification. |
data.detail[].kdMAP | integer | Echoed back as an integer even though the request supplied a string. Clients must accept both types. |
status | integer | 1 on success |
message | string | "Sukses" on success |
uuid | string | Per-call DJP correlation ID |
Example response — soft error
Soft errors on create follow the same HTTP 400 wrapping as inquiry. The original DJP envelope is at body.response with the diagnostic at body.response.message. idBilling, tglKadaluarsa, and billingCreationDate are placeholder values that must not be used.
HTTP/1.1 400 Bad Request
Content-Type: application/json; charset=utf-8
{
"response": {
"requestDate": "2026-01-22 17:36:59",
"bankID": "0923",
...
"idBilling": "0",
"tglKadaluarsa": "2026-01-22 00:00:00",
"billingCreationDate": "2026-01-22 00:00:00",
"type": "0",
"detail": [ ... ],
"status": 0,
"message": "KAP-KJS tidak dapat dibuat melalui layanan mandiri",
"uuid": "1cb7db04-667e-4f00-89bd-aa82e4428932",
"correlation_id": "f0adbfb4-fd9e-4b51-8a09-175b713bc78c",
"status_code": 400
},
"status": 400,
"options": {},
"message": "KAP-KJS tidak dapat dibuat melalui layanan mandiri",
"name": "BadRequestException",
"correlation_id": "f0adbfb4-fd9e-4b51-8a09-175b713bc78c"
}9.5 Error catalogue
DJP returns specific Bahasa Indonesia messages for each business-level failure. The table below lists the messages observed in production and staging traffic, grouped by failure category. All of these surface as HTTP 400 with the DJP envelope at body.response.
Inquiry
| DJP message | Cause |
|---|---|
| NPWP tidak 16 digit, termasuk NPWP tidak ada dalam database | Generic NPWP rejection. May also indicate the (NPWP, kdMAP, kjs) combination is invalid even when the NPWP exists. |
| Failed (undefined atau error lebih dari satu kombinasi) | Ambiguous request — DJP found more than one matching record. Validate the kdMAP / kjs combination. |
| Kombinasi BankID dan Branch ID tidak terdaftar | bankID + branchID is not in DJP's payment-partner registry |
| Tidak mempunyai akses untuk service ini | Authentication error at DJP — typically a bad or stale authCode |
| Terjadi kendala, silahkan hubungi admin! (Code: 01) | DJP-side outage. The DJP body has no other detail. Retry with backoff. |
Create
| DJP message | Cause |
|---|---|
| KAP-KJS tidak dapat dibuat melalui layanan mandiri | The chosen (kdMAP, kjs) is restricted — DJP requires the billing to be issued through an officer-mediated channel, not via PJAP self-service. |
| KAP-KJS tidak dapat digunakan untuk tipe WP tersebut | The (kdMAP, kjs) is not allowed for this taxpayer's classification (individual vs entity, etc.). |
| KJS tidak 3 digit | Malformed kjs — must be exactly three characters. |
| Periode tidak 8 digit | Malformed periode — must be exactly eight characters. See section 9.2.2. |
| Periode tidak sesuai dengan validasi kombinasi KAP-KJS | The chosen periode is invalid for the (kdMAP, kjs) combination. Overloaded message: covers both wrong period structure (e.g. masa1>masa2) and out-of-range periods (e.g. periode in the past). |
| Currency tidak sesuai dengan validasi kombinasi KAP-KJS/Active certificate | currencyCode is not permitted for this (kdMAP, kjs). |
| Kolom "confirm" terisi 1 namun traceID berbeda dengan request awal | Two-step flow violated — when confirm="1" on the second call, the traceId must match the first call. See section 9.2.4. |
9.6 Error handling reference
| Situation | What the client sees |
|---|---|
| Successful inquiry or create | HTTP 200, data.status = 1, data carries the payload |
| DJP business-level failure | HTTP 400, body.response contains the original DJP envelope including data.status = 0 and the Bahasa Indonesia message |
| DJP outage | HTTP 400 with body.message = "Terjadi kendala, silahkan hubungi admin! (Code: 01)". Retry with backoff. |
| Two-step confirm flow violation | HTTP 400 with traceId mismatch error. See section 9.2.4. |
| Authentication failure (Sipajak side) | HTTP 401 |
| NPWP header missing or not registered | HTTP 403 |
| Server error | HTTP 500 |