Withdrawals API
When an escrow is released, the seller's share lands in their RekberPay balance. A withdrawal moves it out to a bank account or an e-wallet. The amount is taken from the balance when the request is made; RekberPay then pays it out and the status moves on from pending.
Endpoints
| Method | Path | Endpoint | Auth |
|---|---|---|---|
GET | /v1/user/balance | Get the balance | Key or session |
GET | /v1/withdrawals/config | Get withdrawal limits | Key or session |
POST | /v1/withdrawals | Request a withdrawal | Key or session |
GET | /v1/withdrawals | List withdrawals | Key or session |
Get the balance
The current balance with recent balance movements, recent withdrawals and 30-day totals.
- Auth: A personal API key (
Authorization: Bearer <key>) or a session cookie withX-CSRF-Tokenon writes. - Rate limit: 300 requests a minute per user, shared by every route.
- Idempotency: Not needed. Reading has no side effects.
Example request
curl https://api.rekberpay.com/v1/user/balance \
-H "Authorization: Bearer $REKBERPAY_API_KEY"const res = await fetch('https://api.rekberpay.com/v1/user/balance', {
headers: {
Authorization: `Bearer ${process.env.REKBERPAY_API_KEY}`,
},
});
const data = await res.json();import os
import requests
resp = requests.get(
"https://api.rekberpay.com/v1/user/balance",
headers={
"Authorization": f"Bearer {os.environ['REKBERPAY_API_KEY']}",
},
timeout=30,
)
print(resp.status_code, resp.json())<?php
$ch = curl_init('https://api.rekberpay.com/v1/user/balance');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('REKBERPAY_API_KEY'),
],
]);
$response = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
curl_close($ch);Example response
200 OK
{
"success": true,
"data": {
"balance": 242500,
"recent_transactions": [
{
"id": "22222222-2222-4222-8222-222222222222",
"type": "release_to_seller",
"amount": "242500.00",
"currency": "IDR",
"status": "success",
"created_at": "2026-10-13T14:23:18.757Z",
"escrow_id": "33333330-3333-4333-8333-333333333333",
"escrow_amount": "250000.00",
"platform_fee": "7500.00"
}
],
"recent_withdrawals": [],
"stats_30d": {
"credit": 242500,
"debit": 0,
"pending_count": 0
}
}
}Errors
| Status | Code | Cause |
|---|---|---|
404 | USER_NOT_FOUND | The account was removed |
401 | UNAUTHORIZED, SESSION_EXPIRED, INVALID_TOKEN | No session or API key, the session expired, or the key is wrong or revoked |
401 | WORKSPACE_KEY_ONLY | A workspace key (rpe_...) was sent. Those only work under /v1/enterprise |
403 | ACCOUNT_INACTIVE, SUSPENDED | The account is closed, being deleted or suspended |
429 | RATE_LIMITED | Over the rate limit. Wait for Retry-After seconds |
Get withdrawal limits
The minimum, maximum and step for an amount, the fee rule, your loyalty discount on the fee and your unused withdrawal fee vouchers. The fee rule here is the one POST /v1/withdrawals charges with. If RekberPay's fee settings are missing or invalid, both fall back to a fixed Rp5.000 with a Rp2.500 floor and a Rp25.000 cap.
- Auth: A personal API key (
Authorization: Bearer <key>) or a session cookie withX-CSRF-Tokenon writes. - Rate limit: 300 requests a minute per user, shared by every route.
- Idempotency: Not needed. Reading has no side effects.
Example request
curl https://api.rekberpay.com/v1/withdrawals/config \
-H "Authorization: Bearer $REKBERPAY_API_KEY"const res = await fetch('https://api.rekberpay.com/v1/withdrawals/config', {
headers: {
Authorization: `Bearer ${process.env.REKBERPAY_API_KEY}`,
},
});
const data = await res.json();import os
import requests
resp = requests.get(
"https://api.rekberpay.com/v1/withdrawals/config",
headers={
"Authorization": f"Bearer {os.environ['REKBERPAY_API_KEY']}",
},
timeout=30,
)
print(resp.status_code, resp.json())<?php
$ch = curl_init('https://api.rekberpay.com/v1/withdrawals/config');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('REKBERPAY_API_KEY'),
],
]);
$response = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
curl_close($ch);Example response
200 OK
{
"success": true,
"data": {
"withdraw_min_idr": 5000,
"withdraw_max_idr": 50000000,
"withdraw_step_idr": 100,
"withdraw_fee_type": "percentage",
"withdraw_fee_value": 1,
"withdraw_fee_min_idr": 1500,
"withdraw_fee_max_idr": 25000000,
"withdraw_fee_loyalty_discount_percent": 0,
"withdraw_fee_vouchers": []
}
}| Field | Type | Description |
|---|---|---|
withdraw_fee_type, withdraw_fee_value | string, number | percentage or fixed, and its value |
withdraw_fee_min_idr, withdraw_fee_max_idr | integer | Floor and cap of the fee. A cap of 0 means withdrawals are free |
withdraw_fee_loyalty_discount_percent | number | Your loyalty level discount on the fee, 0 to 100 |
withdraw_fee_vouchers | array | Your unused one-time withdrawal fee vouchers: redemption_id, voucher_id, code, percent_off, amount_off, max_discount_amount, expires_at |
The fee charged is the rule above, then the loyalty discount, then the voucher that takes off the most (ties go to the one that expires first). A voucher is used up by the withdrawal and comes back if RekberPay rejects it.
Errors
| Status | Code | Cause |
|---|---|---|
401 | UNAUTHORIZED, SESSION_EXPIRED, INVALID_TOKEN | No session or API key, the session expired, or the key is wrong or revoked |
401 | WORKSPACE_KEY_ONLY | A workspace key (rpe_...) was sent. Those only work under /v1/enterprise |
403 | ACCOUNT_INACTIVE, SUSPENDED | The account is closed, being deleted or suspended |
429 | RATE_LIMITED | Over the rate limit. Wait for Retry-After seconds |
Request a withdrawal
Takes the amount from the balance and queues the payout. The amount must be a multiple of withdraw_step_idr, unless you withdraw the whole balance. Check the destination carefully: a payout to a mistyped account cannot be pulled back. Send destination_ack: true once the user confirmed it.
- Auth: A personal API key (
Authorization: Bearer <key>) or a session cookie withX-CSRF-Tokenon writes. - Rate limit: 300 requests a minute per user, shared by every route.
- Idempotency: Send an
Idempotency-Keyheader (UUID v4). A retry with the same key and body returns the first answer withIdempotent-Replay: true.
Body (JSON)
| Name | Type | Required | Description |
|---|---|---|---|
amount | number | Yes | Rupiah, between withdraw_min_idr and withdraw_max_idr |
method | string | Yes | bank_transfer, dana, gopay or crypto |
destination_details | object | Yes | For bank_transfer: bank_name, account_number, account_holder. For dana and gopay: phone. For crypto: network, wallet_address |
destination_ack | boolean | No | true records that the user confirmed this exact destination |
Example request
curl -X POST https://api.rekberpay.com/v1/withdrawals \
-H "Authorization: Bearer $REKBERPAY_API_KEY" \
-H "Idempotency-Key: 33333332-3333-4333-8333-333333333333" \
-H "Content-Type: application/json" \
-d '{
"amount": 100000,
"method": "bank_transfer",
"destination_details": {
"bank_name": "BCA",
"account_number": "1234567890",
"account_holder": "Budi Santoso"
},
"destination_ack": true
}'const res = await fetch('https://api.rekberpay.com/v1/withdrawals', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.REKBERPAY_API_KEY}`,
'Content-Type': 'application/json',
'Idempotency-Key': '33333332-3333-4333-8333-333333333333',
},
body: JSON.stringify({
amount: 100000,
method: "bank_transfer",
destination_details: {
bank_name: "BCA",
account_number: "1234567890",
account_holder: "Budi Santoso"
},
destination_ack: true
}),
});
const data = await res.json();import os
import requests
resp = requests.post(
"https://api.rekberpay.com/v1/withdrawals",
headers={
"Authorization": f"Bearer {os.environ['REKBERPAY_API_KEY']}",
"Idempotency-Key": "33333332-3333-4333-8333-333333333333",
},
json={
"amount": 100000,
"method": "bank_transfer",
"destination_details": {
"bank_name": "BCA",
"account_number": "1234567890",
"account_holder": "Budi Santoso",
},
"destination_ack": True,
},
timeout=30,
)
print(resp.status_code, resp.json())<?php
$ch = curl_init('https://api.rekberpay.com/v1/withdrawals');
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('REKBERPAY_API_KEY'),
'Content-Type: application/json',
'Idempotency-Key: 33333332-3333-4333-8333-333333333333',
],
CURLOPT_POSTFIELDS => json_encode([
'amount' => 100000,
'method' => 'bank_transfer',
'destination_details' => [
'bank_name' => 'BCA',
'account_number' => '1234567890',
'account_holder' => 'Budi Santoso',
],
'destination_ack' => true,
]),
]);
$response = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
curl_close($ch);Example response
201 Created
{
"success": true,
"data": {
"id": "44444442-4444-4444-8444-444444444444",
"user_id": "44444440-4444-4444-8444-444444444444",
"amount": "100000.00",
"fee_amount": "1500.00",
"net_amount": "98500.00",
"method": "bank_transfer",
"destination_details": "{\"bank_name\":\"BCA\",\"account_number\":\"1234567890\",\"account_holder\":\"Budi Santoso\"}",
"status": "pending",
"admin_note": null,
"processed_by": null,
"processed_at": null,
"created_at": "2026-10-13T14:23:18.920Z",
"updated_at": "2026-10-13T14:23:18.920Z",
"risk": {
"action": "allow",
"flags": []
}
}
}| Field | Type | Description |
|---|---|---|
amount | string | Taken from the balance |
fee_amount | string | Withdrawal fee, after your loyalty discount and fee voucher |
net_amount | string | What arrives at the destination |
status | string | pending until RekberPay pays it out |
Errors
| Status | Code | Cause |
|---|---|---|
400 | INVALID_AMOUNT | Below the minimum, above the maximum, or not a multiple of the step |
400 | INVALID_METHOD | method is not one of the four |
400 | MISSING_DESTINATION | destination_details is missing |
400 | FEE_EXCEEDS_AMOUNT | The fee is larger than the amount |
400 | INSUFFICIENT_BALANCE | The balance is too low |
403 | EMAIL_NOT_VERIFIED | You signed up with email and password and have not confirmed the email yet |
403 | RISK_BLOCKED | The withdrawal was stopped by a risk check. Contact support |
404 | USER_NOT_FOUND | The account was removed |
500 | DB_ERROR | The withdrawal was not created. Retry with the same Idempotency-Key |
401 | UNAUTHORIZED, SESSION_EXPIRED, INVALID_TOKEN | No session or API key, the session expired, or the key is wrong or revoked |
401 | WORKSPACE_KEY_ONLY | A workspace key (rpe_...) was sent. Those only work under /v1/enterprise |
403 | CSRF_FAILED | Session call without the matching X-CSRF-Token header |
403 | ACCOUNT_INACTIVE, SUSPENDED | The account is closed, being deleted or suspended |
429 | RATE_LIMITED | Over the rate limit. Wait for Retry-After seconds |
A real INVALID_AMOUNT answer:
400 Bad Request
{
"success": false,
"error": {
"code": "INVALID_AMOUNT",
"message": "Minimum withdraw adalah Rp 5.000"
}
}List withdrawals
Your withdrawals, newest first.
- Auth: A personal API key (
Authorization: Bearer <key>) or a session cookie withX-CSRF-Tokenon writes. - Rate limit: 300 requests a minute per user, shared by every route.
- Idempotency: Not needed. Reading has no side effects.
Query parameters
| Name | Type | Required | Description |
|---|---|---|---|
page | integer | No | Default 1 |
limit | integer | No | 1 to 50. Default 20 |
Example request
curl "https://api.rekberpay.com/v1/withdrawals?limit=5" \
-H "Authorization: Bearer $REKBERPAY_API_KEY"const res = await fetch('https://api.rekberpay.com/v1/withdrawals?limit=5', {
headers: {
Authorization: `Bearer ${process.env.REKBERPAY_API_KEY}`,
},
});
const data = await res.json();import os
import requests
resp = requests.get(
"https://api.rekberpay.com/v1/withdrawals?limit=5",
headers={
"Authorization": f"Bearer {os.environ['REKBERPAY_API_KEY']}",
},
timeout=30,
)
print(resp.status_code, resp.json())<?php
$ch = curl_init('https://api.rekberpay.com/v1/withdrawals?limit=5');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('REKBERPAY_API_KEY'),
],
]);
$response = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
curl_close($ch);Example response
200 OK
{
"success": true,
"data": [
{
"id": "44444442-4444-4444-8444-444444444444",
"user_id": "44444440-4444-4444-8444-444444444444",
"amount": "100000.00",
"fee_amount": "1500.00",
"net_amount": "98500.00",
"method": "bank_transfer",
"destination_details": "{\"bank_name\":\"BCA\",\"account_number\":\"1234567890\",\"account_holder\":\"Budi Santoso\"}",
"status": "pending",
"admin_note": null,
"processed_by": null,
"processed_at": null,
"created_at": "2026-10-13T14:23:18.920Z",
"updated_at": "2026-10-13T14:23:18.920Z"
}
],
"pagination": {
"page": 1,
"limit": 5,
"total": "1"
}
}Errors
| Status | Code | Cause |
|---|---|---|
401 | UNAUTHORIZED, SESSION_EXPIRED, INVALID_TOKEN | No session or API key, the session expired, or the key is wrong or revoked |
401 | WORKSPACE_KEY_ONLY | A workspace key (rpe_...) was sent. Those only work under /v1/enterprise |
403 | ACCOUNT_INACTIVE, SUSPENDED | The account is closed, being deleted or suspended |
429 | RATE_LIMITED | Over the rate limit. Wait for Retry-After seconds |