AntaraPayAPI v1

Part 3–5

Payin

Create payins, read QRIS/VA instructions, fetch the QR image, list payins, and handle statuses.

Create a payin

POST/api/v1/payinspayins:writeResponse 202 Accepted

The API accepts the request to create an instruction; this response does not yet prove that the customer has paid.

FieldRequiredDescription
referenceYesUnique merchant order number.
amount / currencyYesAmount in rupiah and IDR.
channelYesQRIS_GOPAY, QRIS_QOIN, VA_QOIN, or BANK_TRANSFER.
bank_codeYes*Required for VA_QOIN and BANK_TRANSFER; take it from GET /channels. Not sent for QRIS.
customer_referenceNoCustomer ID in the merchant's system, at most 64 characters.
metadataNoExtra information within the limits in Data formats.
Request
POST /api/v1/payins
Authorization: Bearer <api_key>
Idempotency-Key: order-0001-create
Content-Type: application/json

{
  "reference": "ORDER-0001",
  "amount": "100000",
  "currency": "IDR",
  "channel": "QRIS_GOPAY",
  "customer_reference": "CUST-100",
  "metadata": { "order_id": "ORDER-0001" }
}
Response 202
{
  "data": {
    "id": "pin_example_001",
    "reference": "ORDER-0001",
    "status": "QUEUED",
    "status_url": "/api/v1/payins/pin_example_001",
    "created_at": "2026-10-15T05:00:00Z"
  },
  "request_id": "req_example_001"
}
The instruction is created asynchronously. Store the id and reference, then poll the GET details until the instruction is available. Do not request a new QR repeatedly while the status is QUEUED/CREATING. To switch payment method, create a new order/payin only after the payment risk of the old instruction has been handled.

Unknown fields are rejected. The expiry is set by the channel/provider and returned in the details. Clients must not assume a QR or VA always lasts the same duration. An inactive channel or an amount outside the limits is rejected before the intent is accepted.

Payin details

GET/api/v1/payins/{id}payins:readResponse 200

payment_instruction is null while the instruction is not yet available. Fee and net on an unpaid transaction are estimates based on the policy at creation time.

Response 200
{
  "data": {
    "id": "pin_example_001",
    "reference": "ORDER-0001",
    "status": "AWAITING_PAYMENT",
    "channel": "QRIS_GOPAY",
    "amount": "100000",
    "fee": "1500",
    "net": "98500",
    "currency": "IDR",
    "settlement_status": "NOT_ELIGIBLE",
    "resource_version": 2,
    "payment_instruction": {
      "type": "QR",
      "image_path": "/api/v1/payins/pin_example_001/qr",
      "expires_at": "2026-10-15T05:15:00Z"
    },
    "created_at": "2026-10-15T05:00:00Z",
    "updated_at": "2026-10-15T05:00:20Z",
    "paid_at": null
  },
  "request_id": "req_example_002"
}

payment_instruction shapes for VA and bank

VA_QOIN
{
  "type": "VA",
  "bank_code": "BANK_CODE",
  "account_number": "880012345678",
  "account_name": "NAMA PEMBAYARAN",
  "amount": "100000",
  "currency": "IDR",
  "expires_at": "2026-10-15T06:00:00Z"
}
BANK_TRANSFER
{
  "type": "BANK_TRANSFER",
  "bank_code": "BANK_CODE",
  "account_number": "1234567890",
  "account_name": "NAMA REKENING ANTARAPAY",
  "amount": "100000",
  "currency": "IDR",
  "payment_reference": null,
  "expires_at": "2026-10-15T06:00:00Z"
}
The example bank_code, account numbers, names, IDs, and times are illustrative. Display the values returned by the API without replacing the account or adding your own unique code. If payment_reference is present, pass its instructions on to the customer. Do not assume that a proof of payment uploaded by the customer means the payment succeeded.

Fee proposal used in the example: 1.5% on a successful payin, deducted from the merchant's entitlement. Gross Rp100,000 → fee Rp1,500 → net Rp98,500. Use the fee/net values in the response as the integration reference; confirm the effective rate and who bears it at activation.

Fetch the QR image

GET/api/v1/payins/{id}/qrpayins:readResponse 200 image/png

A successful response is a binary PNG with Content-Type: image/png and Cache-Control: private, no-store; on success this endpoint does not return a JSON envelope.

Example request
curl "$ANTARAPAY_BASE_URL/api/v1/payins/pin_example_001/qr" \
  -H "Authorization: Bearer $ANTARAPAY_API_KEY" \
  -H "Accept: image/png" --output qris.png

Your merchant backend fetches the image and serves it through your own endpoint, which authorizes the customer's order. The API key is never sent to the browser. Errors are still JSON:

HTTPCodeMeaning
409QR_NOT_READYThe instruction is not ready; keep polling the details.
409INVALID_STATEThe transaction is no longer awaiting payment.
410QR_EXPIREDThe QR has expired.
422CHANNEL_NOT_QRThe payin channel is not a QRIS channel.

List payins

GET/api/v1/payins?reference=ORDER-0001&limit=50payins:read

Optional filters: reference, channel, status, from, to, cursor, limit. Date filters use created_at. Reference search is an exact match. The list contains summaries without payment_instruction; fetch the details for the instruction.

Response 200
{
  "data": [
    {
      "id": "pin_example_001",
      "reference": "ORDER-0001",
      "status": "SUCCEEDED",
      "channel": "QRIS_GOPAY",
      "amount": "100000",
      "fee": "1500",
      "net": "98500",
      "currency": "IDR",
      "settlement_status": "COMPLETED",
      "created_at": "2026-10-15T05:00:00Z"
    }
  ],
  "pagination": { "next_cursor": null, "has_more": false },
  "request_id": "req_example_003"
}

Payin statuses

StatusClient action
QUEUED / CREATINGShow that the payment is being prepared; wait or poll.
AWAITING_PAYMENTShow the instruction and expiry; wait for server confirmation.
SUCCEEDEDPayment verified; process the order idempotently.
EXPIRED / FAILEDStop showing the old instruction; read the error/details if present.
NEEDS_REVIEWShow that it is being reviewed; do not treat it as successful or blindly retry.

Succeeded does not mean the balance is available

A GET payin SUCCEEDED maps to completed in the webhook, but the funds do not necessarily reach the Active Balance: settlement_status may be WAITING_FUNDS. An EXPIRED payin can become SUCCEEDED after a late payment is verified; process the completed webhook with the newer resource_version.

Suggested polling: every 2 seconds with backoff up to 30 seconds; respect the rate limit. Queue and review statuses are not sent as final webhooks.