e-Bupot

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.

MethodPathFungsi
POST/v2/ebupot/validate-bpu-a0-21Validate withholding receipt — BPPU, BPMP, BP21
POST/v2/ebupot/create-bpu-a0-21Create withholding receipt — BPPU, BPMP, BP21
POST/v2/ebupot/update-bpu-a0-21Update withholding receipt — BPPU, BPMP, BP21
POST/v2/ebupot/cancel-bpu-a0-21Cancel withholding receipt — BPPU, BPMP, BP21
POST/v2/ebupot/validate-bpnr-26Validate withholding receipt — BPNR
POST/v2/ebupot/create-bpnr-26Create withholding receipt — BPNR
POST/v2/ebupot/update-bpnr-26Update withholding receipt — BPNR
POST/v2/ebupot/cancel-bpnr-26Cancel withholding receipt — BPNR
POST/v2/ebupot/validate-cumulative-paymentValidate withholding receipt — BPCY
POST/v2/ebupot/create-cumulative-paymentCreate withholding receipt — BPCY
POST/v2/ebupot/update-cumulative-paymentUpdate withholding receipt — BPCY
POST/v2/ebupot/cancel-cumulative-paymentCancel withholding receipt — BPCY
POST/v2/ebupot/validate-self-paymentValidate withholding receipt — BPSP
POST/v2/ebupot/create-self-paymentCreate withholding receipt — BPSP
POST/v2/ebupot/update-self-paymentUpdate withholding receipt — BPSP
POST/v2/ebupot/cancel-self-paymentCancel withholding receipt — BPSP
POST/v2/ebupot/validate-a1-a2Validate withholding receipt — BPA1, BPA2
POST/v2/ebupot/create-a1-a2Create withholding receipt — BPA1, BPA2
POST/v2/ebupot/update-a1-a2Update withholding receipt — BPA1, BPA2
POST/v2/ebupot/cancel-a1-a2Cancel withholding receipt — BPA1, BPA2
POST/v2/ebupot/verify-documentRefresh DJP-side state of an existing bupot. See section 7.9.
POST/calculationLocal calculator for BPMP and BP21. Unauthenticated. See section 7.10.
POST/calculation/bpa1Local 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-objectsDistinct tax-object descriptions
GET/v1/ref-{variant}/tax-articlesDistinct tax-article codes ("Pasal X")
GET/v1/ref-{variant}/tax-codesDistinct tax-object codes
GET/v1/ref-{variant}/income-tax-statusesDistinct status enums (final / non-final etc.)
GET/v1/ref-{variant}/income-tax-ratesDistinct tax-rate values
GET/v1/ref-{variant}/revenue-codesDistinct 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.

VariantCodeCoversEndpoint family
BPPUBPUBupot Unifikasi — Pemotong/Pemungut. PPh 22, 23, 4(2), 15.bpu-a0-21
BPMPA0Bupot Pegawai Tidak Tetap / Bukan Pegawai. PPh 21 (non-employees).bpu-a0-21
BP2121Bupot PPh 21 — monthly employee withholding (final or non-final).bpu-a0-21
BPNRBPNR26Bupot 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
BPA1A1Bupot Tahunan A1 — year-end employee summary (private sector).a1-a2
BPA2A2Bupot 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

MethodPathOperationVariants served
POST/v2/ebupot/validate-bpu-a0-21Validate (pre-flight)BPPU, BPMP, BP21
POST/v2/ebupot/create-bpu-a0-21CreateBPPU, BPMP, BP21
POST/v2/ebupot/update-bpu-a0-21Update (replacement)BPPU, BPMP, BP21
POST/v2/ebupot/cancel-bpu-a0-21CancelBPPU, BPMP, BP21
POST/v2/ebupot/validate-bpnr-26ValidateBPNR
POST/v2/ebupot/create-bpnr-26CreateBPNR
POST/v2/ebupot/update-bpnr-26UpdateBPNR
POST/v2/ebupot/cancel-bpnr-26CancelBPNR
POST/v2/ebupot/validate-cumulative-paymentValidateBPCY
POST/v2/ebupot/create-cumulative-paymentCreateBPCY
POST/v2/ebupot/update-cumulative-paymentUpdateBPCY
POST/v2/ebupot/cancel-cumulative-paymentCancelBPCY
POST/v2/ebupot/validate-self-paymentValidateBPSP
POST/v2/ebupot/create-self-paymentCreateBPSP
POST/v2/ebupot/update-self-paymentUpdateBPSP
POST/v2/ebupot/cancel-self-paymentCancelBPSP
POST/v2/ebupot/validate-a1-a2ValidateBPA1, BPA2
POST/v2/ebupot/create-a1-a2CreateBPA1, BPA2
POST/v2/ebupot/update-a1-a2UpdateBPA1, BPA2
POST/v2/ebupot/cancel-a1-a2CancelBPA1, BPA2

7.2.2 Cross-cutting endpoints

MethodPathPurpose
POST/v2/ebupot/verify-documentRefresh DJP-side state of an existing bupot. See section 7.9.
POST/calculationLocal calculator for BPMP and BP21. Unauthenticated. See section 7.10.
POST/calculation/bpa1Local 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.

MethodPath patternPurpose
GET/v1/ref-{variant}Full-search across the variant's reference data (paginated)
GET/v1/ref-{variant}/tax-objectsDistinct tax-object descriptions
GET/v1/ref-{variant}/tax-articlesDistinct tax-article codes ("Pasal X")
GET/v1/ref-{variant}/tax-codesDistinct tax-object codes
GET/v1/ref-{variant}/income-tax-statusesDistinct status enums (final / non-final etc.)
GET/v1/ref-{variant}/income-tax-ratesDistinct tax-rate values
GET/v1/ref-{variant}/revenue-codesDistinct 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 valueVariantDetail object expected
BPUBPPUdataDetilBpu
A0BPMPdataDetilA0
21BP21dataDetilBp21

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:

FieldWhereFormatNotes
tglPemotonganAll variants, request bodyDDMMYYYYDate of withholding
tglPembatalanCancel only, request bodyDDMMYYYYDate of cancellation
tanggal_DokumendokReferensi[] in requestDDMMYYYYsnake_case with a CAPITAL D — preserve verbatim
tanggal_DokumendokReferensi[] in verify-document responseYYYY-MM-DD HH:mm:ssFormat CHANGES in the verify-document response — full datetime instead of DDMMYYYY
timestampEvery response resultYYYY-MM-DD HH:mm:ssAsia/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 conceptBPU / BPMP / BP21BPNR / BPCY / BPSP / BPA1 / BPA2
PPh tax articlepasalPPh (capital P)pasalPph (lowercase p)
PPh statusstatusPPh (capital P)statusPph (lowercase p)

Furthermore, the values that statusPPh / statusPph can take are themselves stylised differently per variant:

VariantstatusPPh / 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:

FieldDescription
npwpNikPenandatanganNIK of the human signer authorised at DJP
namaPenandatanganSigner name
dcPenandatanganDJP designation flag — always "1" in observed traffic
serialNumberPenandatanganDJP certificate reference — always "1" in observed traffic
passphrasePenandatanganPlaintext passphrase. Never log. Transmit only over TLS.
userIdDJP-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.

FieldTypeRequiredDescription
fgTransactionstring ("NEW" | "EDIT")YesNEW for an initial issue, EDIT when replacing a previously issued bupot
noBupotstringCond.Empty for NEW; DJP-issued bupot number being replaced for EDIT
idBupotstringCond.Empty for NEW; DJP-issued UUID for EDIT
revNostringYesRevision number. Always "1" in observed traffic.
npwpPemotongstringYesWithholding entity NPWP-16
idTkustringYesTempat Kegiatan Usaha id = NPWP + 6-digit branch suffix
masaPajakstringYesTax month "01"–"12"
tahunPajakstringYesTax year (string in this family)
tglPemotonganstringYesWithholding date — DDMMYYYY
fgNpwpNikbooleanYestrue when the counterparty is identified by NPWP, false when by NIK
npwpstringCond.Counterparty NPWP — required when fgNpwpNik is true
nikstringCond.Counterparty NIK / TKU — required when fgNpwpNik is false. 22-digit padded form (NPWP + 6 zeros) when entity, raw 16-digit NIK when individual.
namastringYesCounterparty name
fgJnsBupotstringYesVariant discriminator — "BPU", "A0", or "21". See section 7.3.2.
dataDetilBpu /dataDetilA0 /dataDetilBp21objectYesVariant-specific detail. Exactly one of the three is populated, matching fgJnsBupot.
npwpNikPenandatanganstringYesSigner NIK
namaPenandatanganstringYesSigner name
dcPenandatanganstringYesAlways "1"
serialNumberPenandatanganstringYesAlways "1"
userIdstringYesDJP user identifier
passphrasePenandatanganstringYesPlaintext 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.

FieldTypeDescription
sertifikatInsentifDipotongstringDJP incentive certificate flag
nomorSertifikatInsentifstringIncentive certificate number when applicable; empty string otherwise
kodeObjekPajakstringTax-object code from /v1/ref-bppu/tax-codes (e.g. "28-404-02")
pasalPPhstringTax article (e.g. "Pasal 4 Ayat 2"). Note CAPITAL P.
statusPPhstring"FINAL" or "NOT_FINAL". Note CAPITAL P.
dppnumberDasar Pengenaan Pajak — base amount
tarifnumberTax rate as percentage (e.g. 7.5 means 7.5%) — note: percentage, not fraction; differs from e-Faktur
pphDipotongnumberTax withheld — dpp × (tarif / 100)
kapstringKode Akun Pajak
kjsstringKode Jenis Setoran
dokReferensiarrayReference 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.

FieldTypeDescription
foreignEmployeebooleantrue when the employee is foreign — triggers passport/country fields
passportNostringPassport number when foreignEmployee is true
countryCodestringISO 3-letter country code
statusPtkpstringPersonal tax-allowance status ("TK/0", "K/0", etc.)
jmlPtkpnumberPTKP amount
posisiJabatanstringPosition / job title
kodeObjekPajakstringTax-object code from /v1/ref-bpmp/tax-codes
pasalPPhstringTax article
penghasilanKotornumberGross income
tarifnumberTax rate as percentage
pphDipotongnumberTax withheld
kapstringKode Akun Pajak
kjsstringKode 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.

FieldTypeDescription
sertifikatInsentifDipotongstringDJP incentive certificate flag
nomorSertifikatInsentifstringIncentive certificate number
kodeObjekPajakstringTax-object code from /v1/ref-bp21/tax-codes
pasalPPhstringTax article
statusPPhstring"FINAL" or "NOT_FINAL"
penghasilanKotorSebelumnyanumberCumulative gross income from earlier periods in the same year
penghasilanKotornumberGross income this period
normaPenghasilannumberNormative-income factor (replaces dpp in this variant)
tarifnumberTax rate as percentage
pphDipotongnumberTax withheld
kapstringKode Akun Pajak
kjsstringKode Jenis Setoran
dokReferensiarrayReference 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

FieldTypeDescription
result.nomorBupotstringDJP-issued bupot number — canonical identifier for subsequent operations
result.idBupotstringDJP-issued UUID — also used to reference the bupot on update/cancel
result.namaObjekPajakstringHuman-readable tax-object description
result.pphDipotongnumberTax withheld (echoes the request value)
result.tanggalSP2DstringGovernment SP2D date — empty in most cases; populated only for government-counterparty bupots
result.approvalCodestringEmpty for normal creates; populated only when DJP signing approval is required
result.timestampstringYYYY-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

MethodPath
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):

FieldTypeDescription
tinDipotongstringForeign tax-identification number (TIN)
namaDipotongstringCounterparty name
alamatDipotongstringCounterparty address
negaraDipotongstringISO 3-letter country code
tglLahirDipotongstringDate of birth — DDMMYYYY; empty for non-individuals
tmptLahirDipotongstringPlace of birth; empty for non-individuals
nomorPasporstringPassport number; empty for non-individuals
nomorKitasKitapstringIndonesian residence permit number when applicable

Detail fields (flat at top level — no wrapper):

FieldNotes
kodeObjekPajakTax-object code
pasalPphAlways lowercase p — DIFFERENT casing from BPU family
statusPphAlways lowercase p. Values: typically "FINAL" for PPh 26 (which is final by definition)
penghasilanBrutoGross income
normaPenghasilanNetoNet-income normative factor
tarifTax rate as percentage
pphDipotongTax withheld
kapKode Akun Pajak — number in BPNR (not string)
kjsKode Jenis Setoran — number in BPNR (not string)
dokReferensiReference 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

MethodPath
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

MethodPath
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:

FieldDescription
penghasilanDariIndonesiaIncome from Indonesian sources
pphDariIndonesiaPPh already withheld from Indonesian sources
penghasilanDariLuarIndonesiaIncome from foreign sources
pphDariLuarIndonesiaForeign tax paid
pph24DapatDikreditkanForeign tax creditable under PPh Article 24
pphDipotongPihakLainTax already withheld by other parties
pphSetorSendiriTax 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

MethodPath
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 valueDetail wrapper usedOther wrapper
A1dataDetilBupotA1dataDetilBupotA2 not present (or null fields)
A2dataDetilBupotA2dataDetilBupotA1 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:

FieldTypeDescription
fgNpwpNikbooleantrue = NPWP, false = NIK
npwpstringEmployee NPWP-16
nikstringEmployee NIK — 16 digits in A1/A2 (not 22 like bpu-a0-21)
namastringEmployee name
jnsKelaminstring"M" or "F"
alamatstringAddress
statusPtkpstringPTKP status ("TK/0", "K/0", etc.)
jmlPtkpnumberNumber of dependents
nominalPtkpnumberPTKP nominal amount

7.8.4 Detail object — BPA1 (private-sector A1)

dataDetilBupotA1 carries the year-end gross-up and salary components:

FieldDescription
biayaJabatanPosition-allowance deduction
fgFasilitasFacility flag
fgKaryawanAsingForeign-employee flag
passportPassport when foreign
kdNegaraCountry code
posisiJabatanJob position
gajiPensiunSalary / pension annual total
tunjanganPPhPPh allowance (when company pays employee's tax)
tunjanganPPhGrossUp"Yes" or "No" — flags the gross-up policy
honorariumHonoraria
premiAsuransiInsurance premium
naturaBenefits in kind (Natura)
tantiemBonusTantiem and bonus
iuranPensiunPension contribution
zakatReligious obligation deduction
tunjanganLainnyaLemburOvertime / 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:

FieldDescription
biayaJabatanPosition-allowance deduction
gapokPensiunBasic salary / pension
iuranPensiunPension contribution
tunjanganIsteriSpouse allowance
tunjanganAnakChild allowance
tunjanganPerbaikanPenghasilanIncome improvement allowance
tunjanganStrukturalFungsionalStructural / functional allowance
nipNrpGovernment employee ID (NIP / NRP) — sensitive
pangkatGolRank / grade
posisiJabatanPosition title
tunjanganBerasRice allowance
penghasilanTetapLainnyaOther fixed income
tunjanganLainnyaOther allowances
zakatReligious 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.

FieldDescription
nomorBupotDJP-issued bupot number
idBupotDJP-issued UUID
namaObjekPajakTax-object description
tunjanganPPhGrossUpEchoes request
noBupotSebelumnyaPrevious bupot reference if any
totalPenghasilanNetoDariBupotSebelumnyaNet income from previous period
totalPenghasilanNetoPph21Net income for PPh 21 calculation
pkpSetahunDisetahunkanAnnualised taxable income
pph21SetahunDisetahunkanAnnualised PPh 21
pph21TerutangPPh 21 owed
pph21DariBupotSebelumnyaPPh 21 already withheld in previous periods
pph21DapatDikreditkanCreditable PPh 21
pph21WithheldDtpGovernment-borne PPh withholding (DTP)
pph21KurangLebihBayarFinal settle-up amount (over/under-payment)
approvalCodeEmpty / null on success
timestampYYYY-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

FieldTypeDescription
npwpPemotongstringWithholding entity NPWP
tahunPajakstringTax year of the bupot being verified
noBupotstringDJP-issued bupot number (from the create response)
idBupotstringDJP-issued UUID (from the create response)
fgJnsBupotstringVariant family being verified — see below
userIdstringDJP 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

MethodPathReturns
GET/v1/ref-{variant}Full-search across all the variant's reference rows
GET/v1/ref-{variant}/tax-objectsDistinct tax_object descriptions
GET/v1/ref-{variant}/tax-articlesDistinct tax_article codes
GET/v1/ref-{variant}/tax-codesDistinct tax_object_code values
GET/v1/ref-{variant}/income-tax-statusesDistinct income_tax_status values (not on ref-bpa2)
GET/v1/ref-{variant}/income-tax-ratesDistinct tax_rate values (not on ref-bpa2)
GET/v1/ref-{variant}/revenue-codesDistinct revenue_code values

7.11.2 Common query parameters

ParameterTypeDefaultDescription
limitinteger25Page size, capped at 100
pageinteger0Zero-indexed page number
keywordstring—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

ValueMeaning
NORMALBupot is in normal effective state
SUBMITTEDBupot has been submitted to DJP
AMENDEDBupot has been amended (a replacement now supersedes it)
AMENDMENTThis bupot IS the amendment of an earlier one
CANCELLEDBupot has been cancelled
INTERFACE_INVALIDBupot was rejected by DJP-side validation

7.12.2 Signing status

ValueMeaning
DONESigning complete
SIGNING_IN_PROGRESSSigning in progress; poll verify-document
FAILEDSigning failed
DJP-SIGN-MASTERAwaiting DJP master signature (specific to multi-stage signing flows)

Examples of the composite statusBupot value:

  • NORMAL-DONE — bupot is effective and fully signed
  • SUBMITTED-SIGNING_IN_PROGRESS — bupot accepted by DJP, signing pending
  • CANCELLED-DONE — cancellation complete and signed
  • INTERFACE_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 messageCause
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 ditemukanThe counterparty NPWP or NIK is not registered with DJP. Validate via VSWP before submission.
fgJnsBupot tidak validThe discriminator value supplied does not match the endpoint family. Confirm the endpoint matches the variant being submitted.

7.14 Error handling reference

SituationWhat the client sees
Successful operationHTTP 200, data.status = "1", data.result carries the variant-specific payload (or no result for cancel)
DJP business-level failureHTTP 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 failureHTTP 401
NPWP header missing or not registeredHTTP 403
Calculation endpoint with unauth-able bodyHTTP 400 — the /calculation endpoints do not require auth but still validate the input shape
Server errorHTTP 500