e-Billing

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.

MethodPathFungsi
POST/v2/ebilling/inquiryValidate NPWP + KAP/KJS and fetch taxpayer name and address
POST/v2/ebilling/createGenerate 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

MethodPathPurpose
POST/v2/ebilling/inquiryValidate NPWP + KAP/KJS and fetch taxpayer name and address
POST/v2/ebilling/createGenerate 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.

EndpointGenerationIdempotency role
POST /v2/ebilling/inquiryFresh Unix epoch in milliseconds, generated per callNone. Re-inquiring with a new traceId returns the same DJP data.
POST /v2/ebilling/createUUIDv4 by default, generated per callIdempotency 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:

periodemasa1masa2tahunMeaning
0112202601122026Tax period spanning January through December 2026
1212202412122024December 2024 only
0106202501062025January 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.

confirmModeBehaviour
"0" (default)Single-stepDJP issues idBilling immediately on the first call. This is the path the great majority of integrations should use.
"1"Two-stepFirst 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

HeaderRequiredDescription
AuthorizationYesHTTP Basic Auth (see section 2.2)
npwpYesOrganisation NPWP (see section 2.3)
Content-TypeYesapplication/json

Request body

FieldTypeRequiredDescription
npwpstringYesThe 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.
bankIDstringYesDJP bank identifier (4 digits). Identifies which DJP-affiliated payment partner this billing will flow through.
branchIDstringYesDJP branch identifier (6 digits) corresponding to the chosen bankID.
kdMAPstringYesDJP tax-account code (Kode Akun Pajak). Six digits.
kjsstringYesDJP tax sub-account code (Kode Jenis Setoran). Three digits.
authCodestringNoDJP authentication token when required by the bank/branch combination. Sipajak forwards this verbatim to DJP.
requestDatestringNo (server-generated)If absent, Sipajak generates the current Asia/Jakarta timestamp. See section 9.2.3.
traceIdstringNo (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

FieldTypeDescription
namaWPstringTaxpayer name from DJP master data
alamatWPstringRegistered address
namaKdMapstringHuman-readable description of the kdMAP (tax account)
namaKjsstringHuman-readable description of the kjs (sub-account)
statusinteger1 on success
messagestring"Sukses" on success
uuidstringPer-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

FieldTypeRequiredDescription
npwpstringYesTaxpayer NPWP being billed
bankIDstringYesDJP bank identifier
branchIDstringYesDJP branch identifier
currencyCodestringYesISO 4217 currency code. Currently DJP accepts IDR.
billingAmountintegerYesTotal payment amount in the smallest currency unit (rupiah, no decimals). Must equal the sum of detail[].numAccount.
numRecordDetailintegerYesAlways 1 in current API. Multi-record billings are not supported via this gateway.
uraianstringYesFree-text description visible on payment receipts. Treat this as a payment memo line.
confirmstringNo"0" (default) for single-step, "1" for two-step. See section 9.2.4.
detailarrayYesExactly one element — see Detail fields below.
authCodestringNoDJP authentication token when required
requestDatestringNo (server-generated)See section 9.2.3
traceIdstringNo (server-generated)See section 9.2.1

Detail fields

Exactly one element. Fields below describe the shape of detail[0].

FieldTypeRequiredDescription
kdMAPstringYesDJP tax-account code (six digits). Note: returned as integer in the response.
kjsstringYesDJP tax sub-account code (three digits)
periodestringYesEight-character MMMMYYYY. See section 9.2.2.
numAccountintegerYesAmount allocated to this detail. Must equal billingAmount when numRecordDetail = 1.
traceIdstringYesMust equal the top-level traceId on the create request.
noSKstring (nullable)ConditionalDJP decision-letter reference (Nomor Surat Keputusan). Required for certain KAP/KJS combinations (e.g. penalty payments after a tax audit).
nopstringConditionalDJP tax-object reference (Nomor Objek Pajak). Required for property-tax KAP/KJS combinations; empty string otherwise.
taxObjectAddressstringConditionalAddress of the tax object. Pairs with nop.
kelurahanstringConditionalSub-district of the tax object
kecamatanstringConditionalDistrict
kabKotastringConditionalRegency or city
provinsistringConditionalProvince

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

FieldTypeDescription
idBillingstringThe 15-digit DJP payment reference. This is the value the taxpayer quotes at the bank or payment gateway.
tglKadaluarsastring (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.
billingCreationDatestring (YYYY-MM-DD HH:mm:ss)DJP-side creation timestamp, distinct from the client-supplied requestDate
typestringDJP-defined billing type code. Observed value: "0". Meaning is documented in DJP's CTAS specification.
data.detail[].kdMAPintegerEchoed back as an integer even though the request supplied a string. Clients must accept both types.
statusinteger1 on success
messagestring"Sukses" on success
uuidstringPer-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 messageCause
NPWP tidak 16 digit, termasuk NPWP tidak ada dalam databaseGeneric 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 terdaftarbankID + branchID is not in DJP's payment-partner registry
Tidak mempunyai akses untuk service iniAuthentication 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 messageCause
KAP-KJS tidak dapat dibuat melalui layanan mandiriThe 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 tersebutThe (kdMAP, kjs) is not allowed for this taxpayer's classification (individual vs entity, etc.).
KJS tidak 3 digitMalformed kjs — must be exactly three characters.
Periode tidak 8 digitMalformed periode — must be exactly eight characters. See section 9.2.2.
Periode tidak sesuai dengan validasi kombinasi KAP-KJSThe 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 certificatecurrencyCode is not permitted for this (kdMAP, kjs).
Kolom "confirm" terisi 1 namun traceID berbeda dengan request awalTwo-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

SituationWhat the client sees
Successful inquiry or createHTTP 200, data.status = 1, data carries the payload
DJP business-level failureHTTP 400, body.response contains the original DJP envelope including data.status = 0 and the Bahasa Indonesia message
DJP outageHTTP 400 with body.message = "Terjadi kendala, silahkan hubungi admin! (Code: 01)". Retry with backoff.
Two-step confirm flow violationHTTP 400 with traceId mismatch error. See section 9.2.4.
Authentication failure (Sipajak side)HTTP 401
NPWP header missing or not registeredHTTP 403
Server errorHTTP 500