API e-Bupot Sipajak membuat dan mengelola bukti potong PPh langsung dari sistem payroll atau ERP kamu: delapan varian DJP, verifikasi dokumen, kalkulator PPh 21 dan BPA1, serta katalog referensi.
| Method | Path | Fungsi |
|---|---|---|
| POST | /v2/ebupot/validate-bpu-a0-21 | Validate withholding receipt — BPPU, BPMP, BP21 |
| POST | /v2/ebupot/create-bpu-a0-21 | Create withholding receipt — BPPU, BPMP, BP21 |
| POST | /v2/ebupot/update-bpu-a0-21 | Update withholding receipt — BPPU, BPMP, BP21 |
| POST | /v2/ebupot/cancel-bpu-a0-21 | Cancel withholding receipt — BPPU, BPMP, BP21 |
| POST | /v2/ebupot/validate-bpnr-26 | Validate withholding receipt — BPNR |
| POST | /v2/ebupot/create-bpnr-26 | Create withholding receipt — BPNR |
| POST | /v2/ebupot/update-bpnr-26 | Update withholding receipt — BPNR |
| POST | /v2/ebupot/cancel-bpnr-26 | Cancel withholding receipt — BPNR |
| POST | /v2/ebupot/validate-cumulative-payment | Validate withholding receipt — BPCY |
| POST | /v2/ebupot/create-cumulative-payment | Create withholding receipt — BPCY |
| POST | /v2/ebupot/update-cumulative-payment | Update withholding receipt — BPCY |
| POST | /v2/ebupot/cancel-cumulative-payment | Cancel withholding receipt — BPCY |
| POST | /v2/ebupot/validate-self-payment | Validate withholding receipt — BPSP |
| POST | /v2/ebupot/create-self-payment | Create withholding receipt — BPSP |
| POST | /v2/ebupot/update-self-payment | Update withholding receipt — BPSP |
| POST | /v2/ebupot/cancel-self-payment | Cancel withholding receipt — BPSP |
| POST | /v2/ebupot/validate-a1-a2 | Validate withholding receipt — BPA1, BPA2 |
| POST | /v2/ebupot/create-a1-a2 | Create withholding receipt — BPA1, BPA2 |
| POST | /v2/ebupot/update-a1-a2 | Update withholding receipt — BPA1, BPA2 |
| POST | /v2/ebupot/cancel-a1-a2 | Cancel withholding receipt — BPA1, BPA2 |
| POST | /v2/ebupot/verify-document | Refresh DJP-side state of an existing bupot. See section 7.9. |
| POST | /calculation | Local calculator for BPMP and BP21. Unauthenticated. See section 7.10. |
| POST | /calculation/bpa1 | Local calculator for BPA1 (year-end gross-up). Unauthenticated. See section 7.10. |
| GET | /v1/ref-{variant} | Full-search across the variant's reference data (paginated) |
| GET | /v1/ref-{variant}/tax-objects | Distinct tax-object descriptions |
| GET | /v1/ref-{variant}/tax-articles | Distinct tax-article codes ("Pasal X") |
| GET | /v1/ref-{variant}/tax-codes | Distinct tax-object codes |
| GET | /v1/ref-{variant}/income-tax-statuses | Distinct status enums (final / non-final etc.) |
| GET | /v1/ref-{variant}/income-tax-rates | Distinct tax-rate values |
| GET | /v1/ref-{variant}/revenue-codes | Distinct revenue-code values |
The e-Bupot module covers withholding-tax receipts (Bukti Potong) across all DJP CTAS variants. It is structured around eight bupot variants grouped into five endpoint families. Two of the families serve multiple variants through an in-body discriminator; the other three are dedicated to a single variant each.
Like e-Billing and SPT — but unlike e-Faktur — this module wraps DJP soft errors as HTTP 400 rather than returning them inside an HTTP 200 envelope. Several conventions specific to e-Bupot are described in section 7.3; read that section before any individual endpoint reference.
7.1 The eight variants
DJP CTAS defines eight kinds of withholding receipt. Sipajak exposes all of them. The variant determines which endpoint family the client uses, which detail object is required in the request body, and which response shape is returned.
| Variant | Code | Covers | Endpoint family |
|---|---|---|---|
BPPU | BPU | Bupot Unifikasi — Pemotong/Pemungut. PPh 22, 23, 4(2), 15. | bpu-a0-21 |
BPMP | A0 | Bupot Pegawai Tidak Tetap / Bukan Pegawai. PPh 21 (non-employees). | bpu-a0-21 |
BP21 | 21 | Bupot PPh 21 — monthly employee withholding (final or non-final). | bpu-a0-21 |
BPNR | BPNR26 | Bupot PPh 26 — Non-Resident. | bpnr-26 |
BPCY | (URL-only) | Bupot Cumulative Payment. Used for SPT-correction line items. | cumulative-payment |
BPSP | (URL-only) | Bupot Self-Payment. Direct deposit (SSP) line items. | self-payment |
BPA1 | A1 | Bupot Tahunan A1 — year-end employee summary (private sector). | a1-a2 |
BPA2 | A2 | Bupot Tahunan A2 — year-end employee summary (government employees). | a1-a2 |
BPPU, BPMP, and BP21 share the bpu-a0-21 endpoint family; the variant is selected by a discriminator field in the request body (see section 7.3.2). BPA1 and BPA2 share the a1-a2 family; the variant is selected by which detail block is populated. BPNR, BPCY, and BPSP each have a dedicated family with no in-body discriminator — the URL itself identifies the variant.
7.2 Endpoint summary
Every variant family supports the same four operations: validate (pre-flight), create, update (replacement), and cancel. Plus three cross-cutting endpoints serve all variants.
7.2.1 Variant operations
| Method | Path | Operation | Variants served |
|---|---|---|---|
| POST | /v2/ebupot/validate-bpu-a0-21 | Validate (pre-flight) | BPPU, BPMP, BP21 |
| POST | /v2/ebupot/create-bpu-a0-21 | Create | BPPU, BPMP, BP21 |
| POST | /v2/ebupot/update-bpu-a0-21 | Update (replacement) | BPPU, BPMP, BP21 |
| POST | /v2/ebupot/cancel-bpu-a0-21 | Cancel | BPPU, BPMP, BP21 |
| POST | /v2/ebupot/validate-bpnr-26 | Validate | BPNR |
| POST | /v2/ebupot/create-bpnr-26 | Create | BPNR |
| POST | /v2/ebupot/update-bpnr-26 | Update | BPNR |
| POST | /v2/ebupot/cancel-bpnr-26 | Cancel | BPNR |
| POST | /v2/ebupot/validate-cumulative-payment | Validate | BPCY |
| POST | /v2/ebupot/create-cumulative-payment | Create | BPCY |
| POST | /v2/ebupot/update-cumulative-payment | Update | BPCY |
| POST | /v2/ebupot/cancel-cumulative-payment | Cancel | BPCY |
| POST | /v2/ebupot/validate-self-payment | Validate | BPSP |
| POST | /v2/ebupot/create-self-payment | Create | BPSP |
| POST | /v2/ebupot/update-self-payment | Update | BPSP |
| POST | /v2/ebupot/cancel-self-payment | Cancel | BPSP |
| POST | /v2/ebupot/validate-a1-a2 | Validate | BPA1, BPA2 |
| POST | /v2/ebupot/create-a1-a2 | Create | BPA1, BPA2 |
| POST | /v2/ebupot/update-a1-a2 | Update | BPA1, BPA2 |
| POST | /v2/ebupot/cancel-a1-a2 | Cancel | BPA1, BPA2 |
7.2.2 Cross-cutting endpoints
| Method | Path | Purpose |
|---|---|---|
| POST | /v2/ebupot/verify-document | Refresh DJP-side state of an existing bupot. See section 7.9. |
| POST | /calculation | Local calculator for BPMP and BP21. Unauthenticated. See section 7.10. |
| POST | /calculation/bpa1 | Local calculator for BPA1 (year-end gross-up). Unauthenticated. See section 7.10. |
7.2.3 Reference catalogues
Each of the eight variants exposes its own reference catalogue. The path prefix carries the variant code; the operations are uniform. Detail in section 7.11.
| Method | Path pattern | Purpose |
|---|---|---|
| GET | /v1/ref-{variant} | Full-search across the variant's reference data (paginated) |
| GET | /v1/ref-{variant}/tax-objects | Distinct tax-object descriptions |
| GET | /v1/ref-{variant}/tax-articles | Distinct tax-article codes ("Pasal X") |
| GET | /v1/ref-{variant}/tax-codes | Distinct tax-object codes |
| GET | /v1/ref-{variant}/income-tax-statuses | Distinct status enums (final / non-final etc.) |
| GET | /v1/ref-{variant}/income-tax-rates | Distinct tax-rate values |
| GET | /v1/ref-{variant}/revenue-codes | Distinct revenue-code values |
7.3 Module conventions
Five conventions apply across the entire e-Bupot module. Internalise these once; the per-family references that follow assume familiarity with each.
7.3.1 Soft errors return HTTP 400
Like e-Billing and SPT — and unlike e-Faktur — e-Bupot wraps DJP business errors as HTTP 400 with a BadRequestException envelope. The original DJP envelope is nested at body.response with the diagnostic at body.response.statusMessage.
HTTP/1.1 400 Bad Request
Content-Type: application/json; charset=utf-8
{
"response": {
"status": "0",
"statusMessage": "tanggal_Dokumen salah!",
"uuid": "08fc9756-d96a-4413-8e61-12fc3eb24066",
"correlation_id": "14645a75-20d4-4cfc-953a-76372ca1dc56",
"status_code": 400
},
"status": 400,
"options": {},
"message": "Bad Request Exception",
"name": "BadRequestException",
"correlation_id": "14645a75-20d4-4cfc-953a-76372ca1dc56"
}Branch error-handling code on HTTP status (200 vs 400) first, then read body.response.statusMessage for the DJP-localised diagnostic.
7.3.2 The fgJnsBupot discriminator
Three variants — BPPU, BPMP, and BP21 — share the bpu-a0-21 endpoint family. The client identifies which variant is being submitted via a top-level fgJnsBupot string in the request body.
| fgJnsBupot value | Variant | Detail object expected |
|---|---|---|
| BPU | BPPU | dataDetilBpu |
| A0 | BPMP | dataDetilA0 |
| 21 | BP21 | dataDetilBp21 |
On verify and cancel operations, BPCY, BPSP, and BPMP all use fgJnsBupot = "BPU" regardless of which create endpoint was used originally — the unified cancel/verify path expects the discriminator even though the create flow does not. BPNR is the exception: its verify operation uses fgJnsBupot = "BPNR26".
7.3.3 Date format and field types
Request-body dates use DDMMYYYY (eight characters, no separator) — the same convention as e-Faktur. Two fields and one nested datetime have specific notes:
| Field | Where | Format | Notes |
|---|---|---|---|
tglPemotongan | All variants, request body | DDMMYYYY | Date of withholding |
tglPembatalan | Cancel only, request body | DDMMYYYY | Date of cancellation |
tanggal_Dokumen | dokReferensi[] in request | DDMMYYYY | snake_case with a CAPITAL D — preserve verbatim |
tanggal_Dokumen | dokReferensi[] in verify-document response | YYYY-MM-DD HH:mm:ss | Format CHANGES in the verify-document response — full datetime instead of DDMMYYYY |
timestamp | Every response result | YYYY-MM-DD HH:mm:ss | Asia/Jakarta local time |
Two further type inconsistencies to be aware of: tahunPajak is a string in BPU/BPMP/BP21 and an integer in BPNR/BPCY/BPSP/BPA1/BPA2; masaPajak is always a two-character string ("01" through "12") regardless of variant.
7.3.4 Field naming case landmines
Important. The same conceptual field uses DIFFERENT capitalisation across variants. This is a DJP-side inconsistency that Sipajak forwards verbatim — clients must match the case exactly per variant.
| Field concept | BPU / BPMP / BP21 | BPNR / BPCY / BPSP / BPA1 / BPA2 |
|---|---|---|
| PPh tax article | pasalPPh (capital P) | pasalPph (lowercase p) |
| PPh status | statusPPh (capital P) | statusPph (lowercase p) |
Furthermore, the values that statusPPh / statusPph can take are themselves stylised differently per variant:
| Variant | statusPPh / statusPph values |
|---|---|
BPU / BPMP / BP21 | "FINAL" or "NOT_FINAL" (uppercase enum form) |
BPCY / BPSP | "Final" or "Tidak Final" (title case, Indonesian) |
BPNR | "FINAL" (uppercase enum form) |
The dokReferensi item field tanggal_Dokumen is also worth highlighting on its own: it is snake_case with a capital D on Dokumen. Not camelCase, not all-lowercase. Preserve verbatim.
7.3.5 Signing
e-Bupot uses the same out-of-band certificate registration as e-Faktur — the .p12 file is uploaded once during organisation onboarding; each create / update / cancel request carries only the plaintext passphrase. The signing fields are:
| Field | Description |
|---|---|
npwpNikPenandatangan | NIK of the human signer authorised at DJP |
namaPenandatangan | Signer name |
dcPenandatangan | DJP designation flag — always "1" in observed traffic |
serialNumberPenandatangan | DJP certificate reference — always "1" in observed traffic |
passphrasePenandatangan | Plaintext passphrase. Never log. Transmit only over TLS. |
userId | DJP-side user identifier |
Sensitive material. passphrasePenandatangan travels as a plaintext body field. The TLS layer is the only protection in transit. Client implementations MUST NOT log this field, MUST NOT echo it in error messages, and SHOULD store it in a secrets-management tool.
7.4 BPPU, BPMP, BP21 — the bpu-a0-21 family
The three highest-volume variants — Bupot Unifikasi (BPPU), Bupot Pegawai Tidak Tetap (BPMP / A0), and PPh 21 monthly (BP21) — share four endpoints. The variant is selected by the fgJnsBupot discriminator (see section 7.3.2). The request envelope is identical across variants; the per-variant detail goes into one of three dataDetil* wrapper objects.
In production traffic, BP21 dominates this family — at the time of writing, 99% of /v2/ebupot/create-bpu-a0-21 traffic carries fgJnsBupot = "21". BPPU and BPMP traffic is concentrated on UAT/staging.
7.4.1 Common request fields
Every operation in this family carries the same top-level fields. The detail object below the common fields is variant-specific.
| Field | Type | Required | Description |
|---|---|---|---|
fgTransaction | string ("NEW" | "EDIT") | Yes | NEW for an initial issue, EDIT when replacing a previously issued bupot |
noBupot | string | Cond. | Empty for NEW; DJP-issued bupot number being replaced for EDIT |
idBupot | string | Cond. | Empty for NEW; DJP-issued UUID for EDIT |
revNo | string | Yes | Revision number. Always "1" in observed traffic. |
npwpPemotong | string | Yes | Withholding entity NPWP-16 |
idTku | string | Yes | Tempat Kegiatan Usaha id = NPWP + 6-digit branch suffix |
masaPajak | string | Yes | Tax month "01"–"12" |
tahunPajak | string | Yes | Tax year (string in this family) |
tglPemotongan | string | Yes | Withholding date — DDMMYYYY |
fgNpwpNik | boolean | Yes | true when the counterparty is identified by NPWP, false when by NIK |
npwp | string | Cond. | Counterparty NPWP — required when fgNpwpNik is true |
nik | string | Cond. | Counterparty NIK / TKU — required when fgNpwpNik is false. 22-digit padded form (NPWP + 6 zeros) when entity, raw 16-digit NIK when individual. |
nama | string | Yes | Counterparty name |
fgJnsBupot | string | Yes | Variant discriminator — "BPU", "A0", or "21". See section 7.3.2. |
dataDetilBpu /dataDetilA0 /dataDetilBp21 | object | Yes | Variant-specific detail. Exactly one of the three is populated, matching fgJnsBupot. |
npwpNikPenandatangan | string | Yes | Signer NIK |
namaPenandatangan | string | Yes | Signer name |
dcPenandatangan | string | Yes | Always "1" |
serialNumberPenandatangan | string | Yes | Always "1" |
userId | string | Yes | DJP user identifier |
passphrasePenandatangan | string | Yes | Plaintext certificate passphrase — see section 7.3.5 |
7.4.2 Detail object — BPPU (fgJnsBupot = "BPU")
The dataDetilBpu wrapper carries the PPh 22/23/4(2)/15 calculation fields.
| Field | Type | Description |
|---|---|---|
sertifikatInsentifDipotong | string | DJP incentive certificate flag |
nomorSertifikatInsentif | string | Incentive certificate number when applicable; empty string otherwise |
kodeObjekPajak | string | Tax-object code from /v1/ref-bppu/tax-codes (e.g. "28-404-02") |
pasalPPh | string | Tax article (e.g. "Pasal 4 Ayat 2"). Note CAPITAL P. |
statusPPh | string | "FINAL" or "NOT_FINAL". Note CAPITAL P. |
dpp | number | Dasar Pengenaan Pajak — base amount |
tarif | number | Tax rate as percentage (e.g. 7.5 means 7.5%) — note: percentage, not fraction; differs from e-Faktur |
pphDipotong | number | Tax withheld — dpp × (tarif / 100) |
kap | string | Kode Akun Pajak |
kjs | string | Kode Jenis Setoran |
dokReferensi | array | Reference document array — see section 7.3.6 |
7.4.3 Detail object — BPMP (fgJnsBupot = "A0")
BPMP uses dataDetilA0. It does not carry a dpp; the PPh is computed against penghasilanKotor (gross income) instead.
| Field | Type | Description |
|---|---|---|
foreignEmployee | boolean | true when the employee is foreign — triggers passport/country fields |
passportNo | string | Passport number when foreignEmployee is true |
countryCode | string | ISO 3-letter country code |
statusPtkp | string | Personal tax-allowance status ("TK/0", "K/0", etc.) |
jmlPtkp | number | PTKP amount |
posisiJabatan | string | Position / job title |
kodeObjekPajak | string | Tax-object code from /v1/ref-bpmp/tax-codes |
pasalPPh | string | Tax article |
penghasilanKotor | number | Gross income |
tarif | number | Tax rate as percentage |
pphDipotong | number | Tax withheld |
kap | string | Kode Akun Pajak |
kjs | string | Kode Jenis Setoran |
Note: dataDetilA0 does NOT carry a dokReferensi array, unlike most other variants. Reference documents are not applicable for non-employee PPh 21 withholdings.
7.4.4 Detail object — BP21 (fgJnsBupot = "21")
BP21 uses dataDetilBp21. It extends the BPPU shape with cumulative income tracking and a normative-income factor.
| Field | Type | Description |
|---|---|---|
sertifikatInsentifDipotong | string | DJP incentive certificate flag |
nomorSertifikatInsentif | string | Incentive certificate number |
kodeObjekPajak | string | Tax-object code from /v1/ref-bp21/tax-codes |
pasalPPh | string | Tax article |
statusPPh | string | "FINAL" or "NOT_FINAL" |
penghasilanKotorSebelumnya | number | Cumulative gross income from earlier periods in the same year |
penghasilanKotor | number | Gross income this period |
normaPenghasilan | number | Normative-income factor (replaces dpp in this variant) |
tarif | number | Tax rate as percentage |
pphDipotong | number | Tax withheld |
kap | string | Kode Akun Pajak |
kjs | string | Kode Jenis Setoran |
dokReferensi | array | Reference document array — see section 7.3.6 |
7.4.5 Example — create BPPU
POST /v2/ebupot/create-bpu-a0-21
POST /v2/ebupot/create-bpu-a0-21 HTTP/1.1
Host: gateway-integration.sipajak.com
Authorization: Basic <base64-encoded credentials>
npwp: 1234567890123000
Content-Type: application/json
{
"fgTransaction": "NEW",
"noBupot": "",
"idBupot": "",
"revNo": "1",
"npwpPemotong": "1234567890123000",
"idTku": "1234567890123000000000",
"masaPajak": "12",
"tahunPajak": "2025",
"fgNpwpNik": true,
"npwp": "2345678901234000",
"nik": "3201234567890001000000",
"nama": "Budi Santoso",
"fgJnsBupot": "BPU",
"tglPemotongan": "01122025",
"dataDetilBpu": {
"sertifikatInsentifDipotong": "9",
"nomorSertifikatInsentif": "",
"kodeObjekPajak": "28-404-02",
"pasalPPh": "Pasal 4 Ayat 2",
"statusPPh": "FINAL",
"dpp": 10000000,
"tarif": 7.5,
"pphDipotong": 750000,
"kap": "411128",
"kjs": "100",
"dokReferensi": [
{
"dokReferensi": "ANNOUNCEMENT",
"nomorDokumen": "REF-2025-001",
"tanggal_Dokumen": "02122025"
}
]
},
"npwpNikPenandatangan": "1234567890123000",
"namaPenandatangan": "Budi Santoso",
"dcPenandatangan": "1",
"serialNumberPenandatangan": "1",
"userId": "user-xxxxxxxx",
"passphrasePenandatangan": "<certificate passphrase>"
}Example response — success
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
{
"status_code": 200,
"message": "OK",
"data": {
"status": "1",
"statusMessage": "Success",
"result": {
"nomorBupot": "25000ISSF",
"idBupot": "c56dd8d5-3468-4fa6-bbbb-d5c6c6a121e5",
"namaObjekPajak": "Deposit interest placed domestically (IDR currency sourced from DHE
tenor 1 month)",
"pphDipotong": 750000,
"tanggalSP2D": "",
"approvalCode": "",
"timestamp": "2025-12-09 11:22:51"
},
"uuid": "2612e728-80a7-4ded-ab70-e13e0cc2ef73"
}
}Result fields
| Field | Type | Description |
|---|---|---|
result.nomorBupot | string | DJP-issued bupot number — canonical identifier for subsequent operations |
result.idBupot | string | DJP-issued UUID — also used to reference the bupot on update/cancel |
result.namaObjekPajak | string | Human-readable tax-object description |
result.pphDipotong | number | Tax withheld (echoes the request value) |
result.tanggalSP2D | string | Government SP2D date — empty in most cases; populated only for government-counterparty bupots |
result.approvalCode | string | Empty for normal creates; populated only when DJP signing approval is required |
result.timestamp | string | YYYY-MM-DD HH:mm:ss, Asia/Jakarta |
7.4.6 Validate, Update, Cancel
Validate, update, and cancel mirror the create request structure. The validate endpoint runs the same DJP-side validation as create but does not persist the bupot — use it as a pre-flight check before committing. Update replaces a previously-issued bupot (fgTransaction = "EDIT"; noBupot and idBupot identify the original). Cancel revokes a previously-issued bupot and carries only the identifying fields plus tglPembatalan.
Cancel request body — minimal:
{
"npwpPemotong": "1234567890123000",
"idTku": "1234567890123000000000",
"tahunPajak": "2025",
"noBupot": "25000ISSF",
"idBupot": "c56dd8d5-3468-4fa6-bbbb-d5c6c6a121e5",
"fgJnsBupot": "BPU",
"tglPembatalan": "15122025",
"npwpNikPenandatangan": "1234567890123000",
"namaPenandatangan": "Budi Santoso",
"dcPenandatangan": "1",
"serialNumberPenandatangan": "1",
"userId": "user-xxxxxxxx",
"passphrasePenandatangan": "<certificate passphrase>"
}Cancel response
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
{
"status_code": 200,
"message": "OK",
"data": {
"status": "1",
"statusMessage": "Success",
"approvalCode": "",
"uuid": "..."
}
}Note: cancel responses do NOT carry a result object. There is no echoed nomorBupot, no timestamp. The bupot's identity is implicit (the client supplied it in the request).
7.5 BPNR — PPh 26 Non-Resident
Bupot PPh 26 for payments to non-resident counterparties. Uses a dedicated endpoint family (no fgJnsBupot in the body) and a distinct counterparty schema oriented around foreign-resident identity rather than NPWP / NIK.
7.5.1 Endpoint summary
| Method | Path |
|---|---|
| POST | /v2/ebupot/validate-bpnr-26 |
| POST | /v2/ebupot/create-bpnr-26 |
| POST | /v2/ebupot/update-bpnr-26 |
| POST | /v2/ebupot/cancel-bpnr-26 |
7.5.2 Delta from the bpu-a0-21 family
BPNR shares the common envelope (fgTransaction, npwpPemotong, idTku, masaPajak, tahunPajak, signing fields). The differences are in counterparty fields and detail-object shape.
Removed (compared to bpu-a0-21):
- fgJnsBupot — not present in BPNR body (URL identifies the variant)
- fgNpwpNik, npwp, nik, nama — replaced by the foreign-resident counterparty fields below
- dataDetilBpu / dataDetilA0 / dataDetilBp21 wrapper — BPNR sends detail fields flat at top level (no wrapper)
Added (foreign-resident counterparty):
| Field | Type | Description |
|---|---|---|
tinDipotong | string | Foreign tax-identification number (TIN) |
namaDipotong | string | Counterparty name |
alamatDipotong | string | Counterparty address |
negaraDipotong | string | ISO 3-letter country code |
tglLahirDipotong | string | Date of birth — DDMMYYYY; empty for non-individuals |
tmptLahirDipotong | string | Place of birth; empty for non-individuals |
nomorPaspor | string | Passport number; empty for non-individuals |
nomorKitasKitap | string | Indonesian residence permit number when applicable |
Detail fields (flat at top level — no wrapper):
| Field | Notes |
|---|---|
kodeObjekPajak | Tax-object code |
pasalPph | Always lowercase p — DIFFERENT casing from BPU family |
statusPph | Always lowercase p. Values: typically "FINAL" for PPh 26 (which is final by definition) |
penghasilanBruto | Gross income |
normaPenghasilanNeto | Net-income normative factor |
tarif | Tax rate as percentage |
pphDipotong | Tax withheld |
kap | Kode Akun Pajak — number in BPNR (not string) |
kjs | Kode Jenis Setoran — number in BPNR (not string) |
dokReferensi | Reference document array — same shape as bpu-a0-21 |
Response shape: identical to bpu-a0-21 except tanggalSP2D is omitted from the result object.
7.6 BPCY — Cumulative Payment
Bupot Cumulative Payment is used for SPT correction line items. Its endpoint family has no in-body discriminator and a simplified counterparty schema — the withholding entity itself is treated as the counterparty (used in scenarios where a previously declared withholding is being corrected).
7.6.1 Endpoint summary
| Method | Path |
|---|---|
| POST | /v2/ebupot/validate-cumulative-payment |
| POST | /v2/ebupot/create-cumulative-payment |
| POST | /v2/ebupot/update-cumulative-payment |
| POST | /v2/ebupot/cancel-cumulative-payment |
7.6.2 Delta from the bpu-a0-21 family
- No fgJnsBupot in request body (URL discriminates)
- No separate counterparty NPWP/NIK fields — the withholding entity's own npwpPemotong is reused for both sides
- Detail fields are flat at the top level (no wrapper); shape is otherwise the BPPU detail shape
- Casing inversion:
pasalPph/statusPph— lowercase p (like BPNR, not BPU) - statusPph value form differs:
"Final"or"Tidak Final"— title case, Indonesian. Not the uppercase enum form. Response shape: as bpu-a0-21 minus tanggalSP2D; approvalCode is null (not empty string) in observed responses.
7.7 BPSP — Self-Payment
Bupot Self-Payment is used when the withholding entity itself remits tax to DJP directly (Surat Setoran Pajak line items). Like BPCY, the counterparty resolves to the withholding entity itself; the detail object extends BPCY's with fields tracking domestic vs foreign income split.
7.7.1 Endpoint summary
| Method | Path |
|---|---|
| POST | /v2/ebupot/validate-self-payment |
| POST | /v2/ebupot/create-self-payment |
| POST | /v2/ebupot/update-self-payment |
| POST | /v2/ebupot/cancel-self-payment |
7.7.2 Delta from BPCY
BPSP shares the BPCY envelope plus seven additional income-split fields in the detail block:
| Field | Description |
|---|---|
penghasilanDariIndonesia | Income from Indonesian sources |
pphDariIndonesia | PPh already withheld from Indonesian sources |
penghasilanDariLuarIndonesia | Income from foreign sources |
pphDariLuarIndonesia | Foreign tax paid |
pph24DapatDikreditkan | Foreign tax creditable under PPh Article 24 |
pphDipotongPihakLain | Tax already withheld by other parties |
pphSetorSendiri | Tax remitted directly by the taxpayer |
Casing rules identical to BPCY: lowercase pasalPph / statusPph; title-case Indonesian status values.
7.8 BPA1, BPA2 — Annual Employee Year-End
BPA1 (private-sector employees) and BPA2 (government employees) cover the year-end summary of an employee's PPh 21 withholdings. They share a single endpoint family. The variant is selected by which of two detail blocks is populated in the request body.
7.8.1 Endpoint summary
| Method | Path |
|---|---|
| POST | /v2/ebupot/validate-a1-a2 |
| POST | /v2/ebupot/create-a1-a2 |
| POST | /v2/ebupot/update-a1-a2 |
| POST | /v2/ebupot/cancel-a1-a2 |
7.8.2 Variant selection by payload
The request body carries a fgJnsBupot field set to "A1" or "A2" plus exactly one populated detail block:
| fgJnsBupot value | Detail wrapper used | Other wrapper |
|---|---|---|
| A1 | dataDetilBupotA1 | dataDetilBupotA2 not present (or null fields) |
| A2 | dataDetilBupotA2 | dataDetilBupotA1 present with all-null fields (observed in real payloads) |
7.8.3 Counterparty fields (employee)
The counterparty schema is richer than for the other families — these endpoints serve year-end aggregates for known employees:
| Field | Type | Description |
|---|---|---|
fgNpwpNik | boolean | true = NPWP, false = NIK |
npwp | string | Employee NPWP-16 |
nik | string | Employee NIK — 16 digits in A1/A2 (not 22 like bpu-a0-21) |
nama | string | Employee name |
jnsKelamin | string | "M" or "F" |
alamat | string | Address |
statusPtkp | string | PTKP status ("TK/0", "K/0", etc.) |
jmlPtkp | number | Number of dependents |
nominalPtkp | number | PTKP nominal amount |
7.8.4 Detail object — BPA1 (private-sector A1)
dataDetilBupotA1 carries the year-end gross-up and salary components:
| Field | Description |
|---|---|
biayaJabatan | Position-allowance deduction |
fgFasilitas | Facility flag |
fgKaryawanAsing | Foreign-employee flag |
passport | Passport when foreign |
kdNegara | Country code |
posisiJabatan | Job position |
gajiPensiun | Salary / pension annual total |
tunjanganPPh | PPh allowance (when company pays employee's tax) |
tunjanganPPhGrossUp | "Yes" or "No" — flags the gross-up policy |
honorarium | Honoraria |
premiAsuransi | Insurance premium |
natura | Benefits in kind (Natura) |
tantiemBonus | Tantiem and bonus |
iuranPensiun | Pension contribution |
zakat | Religious obligation deduction |
tunjanganLainnyaLembur | Overtime / other allowances |
The request body also carries year-end aggregate fields at the TOP level (not inside the detail wrapper): pkpSetahunDisetahunkan, pph21Terutang, pph21DapatDikreditkan, pph21DariBupotSebelumnya, pph21KurangLebihBayar, pph21SetahunDisetahunkan, totalPenghasilanNeto*, totalPenghasilanBruto, totalPengurang, blnPenghasilanDisetahunkan, noBupotSebelumnya, pph21WithheldDtp.
7.8.5 Detail object — BPA2 (government A2)
dataDetilBupotA2 has government-employee-specific fields and a different income structure:
| Field | Description |
|---|---|
biayaJabatan | Position-allowance deduction |
gapokPensiun | Basic salary / pension |
iuranPensiun | Pension contribution |
tunjanganIsteri | Spouse allowance |
tunjanganAnak | Child allowance |
tunjanganPerbaikanPenghasilan | Income improvement allowance |
tunjanganStrukturalFungsional | Structural / functional allowance |
nipNrp | Government employee ID (NIP / NRP) — sensitive |
pangkatGol | Rank / grade |
posisiJabatan | Position title |
tunjanganBeras | Rice allowance |
penghasilanTetapLainnya | Other fixed income |
tunjanganLainnya | Other allowances |
zakat | Religious obligation deduction |
7.8.6 Response shape — BPA1 / BPA2 create
Different response shape. Unlike the other variant families which return ~7 fields in result, BPA1 / BPA2 create responses return 16 fields. Response result includes the year-end aggregates that DJP computed server-side, not just the bupot identifier. Plan client-side parsing accordingly.
| Field | Description |
|---|---|
nomorBupot | DJP-issued bupot number |
idBupot | DJP-issued UUID |
namaObjekPajak | Tax-object description |
tunjanganPPhGrossUp | Echoes request |
noBupotSebelumnya | Previous bupot reference if any |
totalPenghasilanNetoDariBupotSebelumnya | Net income from previous period |
totalPenghasilanNetoPph21 | Net income for PPh 21 calculation |
pkpSetahunDisetahunkan | Annualised taxable income |
pph21SetahunDisetahunkan | Annualised PPh 21 |
pph21Terutang | PPh 21 owed |
pph21DariBupotSebelumnya | PPh 21 already withheld in previous periods |
pph21DapatDikreditkan | Creditable PPh 21 |
pph21WithheldDtp | Government-borne PPh withholding (DTP) |
pph21KurangLebihBayar | Final settle-up amount (over/under-payment) |
approvalCode | Empty / null on success |
timestamp | YYYY-MM-DD HH:mm:ss |
Note: pphDipotong and tanggalSP2D are NOT present in BPA1 / BPA2 responses (which the prior families do return). Postman's example response — modelled on BPPU — is not representative for A1 / A2 and should be ignored when integrating these variants.
7.9 verify-document
POST /v2/ebupot/verify-document
Refreshes the DJP-side state of a previously-issued bupot. Used for reconciliation — to confirm a bupot's status after creation, after a cancel, or as part of a periodic batch check. Returns the current DJP state including the bupot's signing status.
7.9.1 Request
| Field | Type | Description |
|---|---|---|
npwpPemotong | string | Withholding entity NPWP |
tahunPajak | string | Tax year of the bupot being verified |
noBupot | string | DJP-issued bupot number (from the create response) |
idBupot | string | DJP-issued UUID (from the create response) |
fgJnsBupot | string | Variant family being verified — see below |
userId | string | DJP user identifier |
fgJnsBupot on verify routes all variants through one path. The verify-document endpoint applies regardless of which create endpoint originally issued the bupot. The fgJnsBupot value on verify is BPU for BPPU/BPMP/BP21/BPCY/BPSP, and BPNR26 for BPNR. There is no value for A1/A2 verify in current code — verify these via the year-end reconciliation reports rather than this endpoint.
7.9.2 Example
POST /v2/ebupot/verify-document HTTP/1.1
Host: gateway-integration.sipajak.com
Authorization: Basic <base64-encoded credentials>
npwp: 1234567890123000
Content-Type: application/json
{
"npwpPemotong": "1234567890123000",
"tahunPajak": "2025",
"noBupot": "25000IFL6",
"idBupot": "a983bdea-ad66-4660-a002-9bc9d58c093f",
"fgJnsBupot": "BPU",
"userId": "user-xxxxxxxx"
}HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
{
"status_code": 200,
"message": "OK",
"data": {
"status": "1",
"statusMessage": "Success",
"result": {
"idPenerimaPenghasilan": "2345678901234000",
"statusBupot": "NORMAL-DONE",
"kodeObjekPajak": "24-104-05",
"pphDipotong": 20000,
"dokReferensi": [
{
"dokReferensi":
"https://coretaxdjp.pajak.go.id/.../DocumentExternalLink/6c52b2b3-...",
"nomorDokumen": "REF-2025-001",
"tanggal_Dokumen": "2025-11-25 00:00:00"
}
],
"timestamp": "2025-12-10 08:40:39"
},
"uuid": "..."
}
}Two specifics worth noting on the response. First, the result.statusBupot value is a hyphen-joined composite of two enums: <document-status>-<signing-status>. "NORMAL-DONE" means document status NORMAL with signing status DONE. The full enum is documented in section 7.12. Second, the tanggal_Dokumen format inside dokReferensi here is YYYY-MM-DD HH:mm:ss, NOT the DDMMYYYY used in create requests — the verify response always uses the full datetime form.
7.10 Calculation utilities
Two local calculator endpoints help clients compute PPh 21 amounts before issuing a bupot. They run pure math — they do not call DJP, do not persist anything, and do not require authentication. Treat them as utilities, not part of the transactional flow.
Unauthenticated by design. These two endpoints are intentionally public — no Basic Auth, no npwp header required. They exist as helper calculators for client integrations and consumer-facing tooling. The intentional public exposure is consistent with the fact that the calculations are pure functions of the input — no DJP credential, no PII access. Do not infer that other Sipajak endpoints are similarly unauthenticated.
7.10.1 PPh 21 / BPMP calculator
POST /calculation
Computes monthly PPh 21 owed under the standard non-final brackets. Used by BPMP (employee withholding) and BP21 (PPh 21 monthly) workflows to derive pphDipotong before submission.
Request fields broadly mirror the dataDetilBp21 / dataDetilA0 input shape — gross income, PTKP status, deductions, etc. — and the response returns the computed tax with a per-bracket breakdown. The response is wrapped in the standard Sipajak {status_code, message, data} envelope.
7.10.2 BPA1 year-end calculator
POST /calculation/bpa1
Computes the year-end gross-up PPh 21 settlement for BPA1. Implements the progressive bracket schedule plus the gross-up factor when tunjanganPPhGrossUp is enabled.
7.11 Reference catalogues
Each variant has its own reference catalogue exposed under /v1/ref-{variant}/* — an unauthenticated set of read-only GETs used to populate client UI dropdowns and validate input. Eight variant prefixes are available: bppu, bpnr, bpcy, bpsp, bp21, bpmp, bpa1, bpa2.
7.11.1 Endpoint pattern
| Method | Path | Returns |
|---|---|---|
| GET | /v1/ref-{variant} | Full-search across all the variant's reference rows |
| GET | /v1/ref-{variant}/tax-objects | Distinct tax_object descriptions |
| GET | /v1/ref-{variant}/tax-articles | Distinct tax_article codes |
| GET | /v1/ref-{variant}/tax-codes | Distinct tax_object_code values |
| GET | /v1/ref-{variant}/income-tax-statuses | Distinct income_tax_status values (not on ref-bpa2) |
| GET | /v1/ref-{variant}/income-tax-rates | Distinct tax_rate values (not on ref-bpa2) |
| GET | /v1/ref-{variant}/revenue-codes | Distinct revenue_code values |
7.11.2 Common query parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
limit | integer | 25 | Page size, capped at 100 |
page | integer | 0 | Zero-indexed page number |
keyword | string | — | Free-text search across the queried column |
7.11.3 Response envelope
{
"status_code": 200,
"message": "OK",
"data": {
"count": 166,
"limit": 10,
"page": 0,
"data": [
{ "tax_object": "Bunga Deposito yang Ditempatkan di Dalam Negeri (IDR, tenor 1 bulan)"
},
{ "tax_object": "Bunga Deposito yang Ditempatkan di Dalam Negeri (IDR, tenor 3 bulan)"
}
]
}
}Schema diverges by variant. The per-variant reference tables do not share an identical schema. ref-bppu through ref-bp21 follow the canonical seven-category model. ref-bpa1 substitutes a gross_up factor for tax_rate. ref-bpa2 is minimal — it lacks facility_code, facility_name, tax_rate, tax_base, and income_tax_status entirely. Client code that consumes references should branch on variant or accept the missing columns gracefully.
7.11.4 No client-side caching guidance
Reference data changes infrequently (typically annually, when DJP issues an updated bracket table). Aggressive caching is safe — daily or weekly refresh is appropriate. Reference endpoints do not appear in audit logs (no decorator), so they cannot be debugged via the standard reconciliation flow — keep this in mind when troubleshooting integrations.
7.12 Status enums
The verify-document response's result.statusBupot field is a hyphen-joined composite of a document-status enum and a signing-status enum: <statusBupot>-<statusSigningBupot>. Both halves are listed below.
7.12.1 Document status
| Value | Meaning |
|---|---|
NORMAL | Bupot is in normal effective state |
SUBMITTED | Bupot has been submitted to DJP |
AMENDED | Bupot has been amended (a replacement now supersedes it) |
AMENDMENT | This bupot IS the amendment of an earlier one |
CANCELLED | Bupot has been cancelled |
INTERFACE_INVALID | Bupot was rejected by DJP-side validation |
7.12.2 Signing status
| Value | Meaning |
|---|---|
DONE | Signing complete |
SIGNING_IN_PROGRESS | Signing in progress; poll verify-document |
FAILED | Signing failed |
DJP-SIGN-MASTER | Awaiting DJP master signature (specific to multi-stage signing flows) |
Examples of the composite statusBupot value:
NORMAL-DONE— bupot is effective and fully signedSUBMITTED-SIGNING_IN_PROGRESS— bupot accepted by DJP, signing pendingCANCELLED-DONE— cancellation complete and signedINTERFACE_INVALID-FAILED— bupot rejected; check the DJP error returned at create time
7.13 Error catalogue
DJP returns Bahasa Indonesia messages for business-level failures via body.response.statusMessage. The table below lists representative messages observed in production and staging traffic on the bpu-a0-21 endpoints. The message space is shared across variants — counterparty errors, period errors, and validation errors follow the same pattern.
| DJP message | Cause |
|---|---|
| tanggal_Dokumen salah! | The dokReferensi[].tanggal_Dokumen value is malformed or in the wrong format. Verify DDMMYYYY (eight chars, no separator). |
| Sudah ada pemotongan BPMP pada masa pajak tersebut... | A BPMP for the same counterparty and tax period already exists. Use update (EDIT) instead of create, or first cancel the existing record. |
| Tindakan tidak diizinkan karena Dokumen sedang dalam Proses Keberatan! | The target bupot is currently under a DJP objection process; operations are locked until the objection resolves at DJP. |
| Wajib Pajak tidak ditemukan | The counterparty NPWP or NIK is not registered with DJP. Validate via VSWP before submission. |
| fgJnsBupot tidak valid | The discriminator value supplied does not match the endpoint family. Confirm the endpoint matches the variant being submitted. |
7.14 Error handling reference
| Situation | What the client sees |
|---|---|
| Successful operation | HTTP 200, data.status = "1", data.result carries the variant-specific payload (or no result for cancel) |
| DJP business-level failure | HTTP 400, body.response.statusMessage carries the diagnostic. body.name = "BadRequestException". |
| Validation error (Sipajak-side) | HTTP 400 with a NestJS validation envelope. body.response is the standard NestJS validation error shape, not the DJP envelope. |
| Authentication failure | HTTP 401 |
| NPWP header missing or not registered | HTTP 403 |
| Calculation endpoint with unauth-able body | HTTP 400 — the /calculation endpoints do not require auth but still validate the input shape |
| Server error | HTTP 500 |