Platform mode (PaaS)
The partner API for marketplaces. You create and link users, open marketplace deals, send buyers to checkout, report shipping, follow disputes and pay out balances. Start with Platform mode (PaaS) to see which part does what.
Basics
- Base URL:
https://api.rekberpay.com/v1/enterprise/platform. Sandbox and live use the same URL; the key decides the workspace and its mode (rpe_test_for Sandbox,rpe_live_for Live). Every read and write stays in that mode. - Platform must be on in the request mode. Each mode has its own switch and freeze. A mode RekberPay switched off gets
404 NOT_FOUNDon every route here, and a frozen mode gets423 PLATFORM_FROZEN. Live needs verified KYB (403 KYB_REQUIRED); Sandbox does not. Read both modes' state withGET /v1/enterprise/platform-status. - Credentials. A workspace key (
rpe_test_orrpe_live_) from your server, or a dashboard session. A personal key or an app session gets403 KEY_NOT_ALLOWED. A key sent with a browserOriginorSec-Fetch-Siteheader gets403 KEY_IN_BROWSER. See Keep your keys safe. - Scopes. Each route names its Platform scope. A key needs the scope on the key, the scope in your
allowed_scopes, andwritefor any change. The member who made the key must still hold a role that allows it:operatoror higher forusers,orders,kycandratings;financeor higher fororders:confirm,balance:readandwithdraw. - Idempotency. Every
POST,PATCHandDELETEfrom a key needs anIdempotency-Key(UUID v4). Answers are kept for 24 hours; only2xxanswers are kept, so a refused call can be retried with the same key. - Users are refs. You name people by your
partner_user_ref. Internal user ids never appear in answers or webhooks. - Out of scope is 404. Another partner's data, staff accounts and users who have not linked to you answer
404 NOT_FOUND. While a link ispending_consentorrevoked, every call about that ref answers404except Get a user and Create or link a user. - Money is whole rupiah. Bank numbers are always masked.
- Sandbox. No email is sent and no gateway is charged. Links that an email would carry come back in the answer (
connect_url,email_change_url,claim_url,release_notice_url). Those answers are kept in the idempotency store for 24 hours like any other answer.
Endpoints
| Method | Path | Endpoint | Scope |
|---|---|---|---|
GET | /v1/enterprise/platform/settings | Get your Platform settings | none |
POST | /v1/enterprise/platform/users | Create or link a user | users |
GET | /v1/enterprise/platform/users/:ref | Get a user | users |
PATCH | /v1/enterprise/platform/users/:ref | Update a user | users |
DELETE | /v1/enterprise/platform/users/:ref | Unlink a user | users |
POST | /v1/enterprise/platform/users/:ref/claim-link | Create a claim link | users |
POST | /v1/enterprise/platform/users/:ref/kyc | Submit KYC | kyc |
GET | /v1/enterprise/platform/users/:ref/kyc | Get KYC status | kyc |
POST | /v1/enterprise/platform/users/:ref/kyc/simulate | Simulate a KYC result (sandbox) | kyc |
POST | /v1/enterprise/platform/deals | Create a deal | orders |
GET | /v1/enterprise/platform/deals/:id | Get a deal | orders |
GET | /v1/enterprise/platform/deals | List deals | orders |
POST | /v1/enterprise/platform/deals/:id/cancel | Cancel a deal | orders |
POST | /v1/enterprise/platform/deals/:id/milestones/:mid/checkout | Get a checkout link | orders |
POST | /v1/enterprise/platform/deals/:id/milestones/:mid/cancel | Cancel a milestone | orders |
POST | /v1/enterprise/platform/deals/:id/milestones/:mid/ship | Ship a milestone | orders |
POST | /v1/enterprise/platform/deals/:id/milestones/:mid/confirm | Confirm receipt | orders:confirm |
POST | /v1/enterprise/platform/deals/:id/milestones/:mid/extend | Extend the auto release | orders |
POST | /v1/enterprise/platform/deals/:id/milestones/:mid/dispute | Open a dispute | orders |
POST | /v1/enterprise/platform/deals/:id/milestones/:mid/dispute/evidence | Add dispute evidence | orders |
GET | /v1/enterprise/platform/users/:ref/balance | Read a balance | balance:read |
GET | /v1/enterprise/platform/users/:ref/bank-accounts | List bank accounts | withdraw |
POST | /v1/enterprise/platform/users/:ref/bank-accounts | Add a bank account | withdraw |
POST | /v1/enterprise/platform/users/:ref/withdrawals | Request a withdrawal | withdraw |
GET | /v1/enterprise/platform/users/:ref/withdrawals/:id | Get a withdrawal | withdraw |
POST | /v1/enterprise/platform/deals/:id/ratings | Rate a completed deal | ratings |
GET | /v1/enterprise/platform/deals/:id/ratings | Get a deal's ratings | ratings |
GET | /v1/enterprise/platform/users/:ref/reputation | Get a user's reputation | users |
PUT | /v1/enterprise/platform/settings/profile-visibility | Show unclaimed profiles (dashboard) | none, dashboard only |
Sandbox shortcuts
These work only in Sandbox and answer 404 in Live. They let you run a whole order in minutes instead of days.
| Call | Scope | What it does |
|---|---|---|
POST /v1/platform/checkout/:token/simulate | none, the checkout token | Pays a milestone with test money |
POST /v1/enterprise/platform/users/:ref/kyc/simulate | kyc | Approves or rejects a pending KYC |
POST /v1/enterprise/platform/deals/:id/milestones/:mid/simulate/advance-time | orders | Releases a shipped or release_pending milestone now, as if its time ran out |
POST /v1/enterprise/platform/users/:ref/bank-accounts/:id/simulate/skip-cooling-off | withdraw | Ends a payout bank account's cooling-off now |
Rate limits
Platform budgets are per key (per member for a dashboard session). On top of them, every call counts against the shared 300 requests a minute of the member who made the key, so all keys made by one member, and that member's dashboard session, share those 300. For high volume, make keys from different members.
| Routes | Budget |
|---|---|
| Reads: settings, users, KYC status, deals, balances, bank accounts, withdrawals, ratings, reputation | 120 a minute |
| Deal and milestone writes: create, cancel, checkout, ship, extend, dispute, evidence, ratings, profile visibility | 60 a minute |
| User writes: create, update, unlink, claim links, KYC submit and simulate | 30 a minute |
| Money: confirm, add a bank account, request a withdrawal, and the two sandbox time shortcuts | 30 a minute |
Over budget you get 429 RATE_LIMITED with a Retry-After header.
Errors every Platform route can answer
| Status | Code | Cause | What to do |
|---|---|---|---|
400 | IDEMPOTENCY_KEY_REQUIRED | A key write without Idempotency-Key | Send a UUID v4, and reuse it on retries |
400 | IDEMPOTENCY_KEY_INVALID | The key is not a UUID v4 | Send a UUID v4 |
401 | UNAUTHORIZED, INVALID_TOKEN | No credential, a wrong or revoked key, or a checkout, claim or connect token sent as a key | Send a valid workspace key |
401 | KEY_IP_NOT_ALLOWED | The key has an IP allowlist and this address is not on it | Call from an allowed address |
403 | KEY_IN_BROWSER | A workspace key sent from a browser (Origin or Sec-Fetch-Site header) | Call from your server only |
403 | KEY_NOT_ALLOWED | A personal API key, an app device session or a WhatsApp session | Use a workspace key or the dashboard |
403 | SCOPE_REQUIRED | The key lacks the scope, your terms do not allow it, or a read-only key tried to write | Make a key with the scope; check allowed_scopes |
403 | FORBIDDEN | The member who made the key no longer has a role that allows the scope | Make a new key from a member with the right role |
403 | KYB_REQUIRED | A Live request of a workspace without verified KYB | Finish business verification |
403 | LIVE_LOCKED | A Live key while the workspace's Live is locked | Work in Sandbox until Live unlocks |
403 | KEY_MODE_MISMATCH | X-Enterprise-Mode names the other mode than the key's | Remove the header, or use a key of that mode |
403 | IP_ALLOWLIST_REQUIRED | A Live key with a Platform scope whose IP allowlist RekberPay has not approved yet | Send your server IPs on the key in the dashboard and wait for approval (going live) |
404 | NOT_FOUND | RekberPay switched Platform off for your workspace in this mode, or the object is not in your scope (another partner or the other mode) | Check the id or ref |
409 | CONCURRENT_MODIFICATION | Two writes raced and this one was rolled back whole. Nothing changed | Read again and retry, also with the same Idempotency-Key |
409 | IDEMPOTENCY_KEY_IN_FLIGHT | The first call with this key is still running | Wait, then retry |
422 | IDEMPOTENCY_KEY_CONFLICT | The same key was used with another JSON body | Use a new key for a new request |
423 | PLATFORM_FROZEN | RekberPay paused your Platform access | Contact your account manager |
429 | RATE_LIMITED | Over a budget | Wait for Retry-After seconds |
Get your Platform settings
Your Platform terms as RekberPay set them: the fee, the default fee payer, the daily cap, the confirm delay and the scopes your keys may hold. Platform is on for every workspace with the standard terms until RekberPay changes them. If RekberPay switched Platform off for your workspace this call answers 404, and while RekberPay has frozen your access it answers 423 PLATFORM_FROZEN, so a successful answer always shows enabled: true and frozen: false.
- Auth: A workspace API key (
Authorization: Bearer rpe_...) sent from your server, or a dashboard session. Personal API keys and app sessions get403 KEY_NOT_ALLOWED. - Scope: None. Any key or member of the workspace may read it.
- Rate limit: 120 a minute per key, plus the shared 300 a minute of the member who made the key.
Example request
curl "https://api.rekberpay.com/v1/enterprise/platform/settings" \
-H "Authorization: Bearer $REKBERPAY_WORKSPACE_KEY"const res = await fetch('https://api.rekberpay.com/v1/enterprise/platform/settings', {
headers: {
Authorization: `Bearer ${process.env.REKBERPAY_WORKSPACE_KEY}`,
},
});
const data = await res.json();import os
import requests
resp = requests.get(
"https://api.rekberpay.com/v1/enterprise/platform/settings",
headers={
"Authorization": f"Bearer {os.environ['REKBERPAY_WORKSPACE_KEY']}",
},
timeout=30,
)
print(resp.status_code, resp.json())<?php
$ch = curl_init('https://api.rekberpay.com/v1/enterprise/platform/settings');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('REKBERPAY_WORKSPACE_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": {
"enabled": true,
"frozen": false,
"fee_rate_bps": 200,
"fee_min_idr": 5000,
"default_fee_bearer": "buyer",
"default_split_buyer_pct": 50,
"daily_cap_idr": 50000000,
"confirm_delay_hours": 6,
"allowed_scopes": [
"users",
"kyc",
"orders",
"balance:read",
"withdraw",
"ratings"
],
"show_unclaimed_profiles": false,
"show_unclaimed_consent_at": null,
"show_unclaimed_consent_version": null,
"show_unclaimed_changed_by": null
}
}| Field | Meaning |
|---|---|
fee_rate_bps, fee_min_idr | The RekberPay fee per deal: 200 basis points is 2 percent, with this minimum in rupiah |
default_fee_bearer, default_split_buyer_pct | Who pays the fee when a deal does not say: buyer, seller or split (the buyer's percent) |
daily_cap_idr | Most new deal value per WIB day, or null for no cap. Cancelled deals do not count |
confirm_delay_hours | How long a release waits after your confirm |
allowed_scopes | The Platform scopes a key of yours may hold |
show_unclaimed_profiles | Whether accounts you created that nobody claimed yet have a public profile. Off by default; see Show unclaimed profiles |
show_unclaimed_consent_at, show_unclaimed_consent_version | When the switch was last changed, and the consent statement version ticked when it was turned on (null while it is off, or after RekberPay changed it) |
show_unclaimed_changed_by | Who last set it: a member's name, or RekberPay |
Errors
| Status | Code | Cause |
|---|---|---|
| Plus the errors every Platform route can answer |
Create or link a user
Creates a RekberPay account for a new email and links it to you at once, or asks a person who already has an account to link (pending_consent). See Create and link users.
| Case | Status | link_status |
|---|---|---|
| New email: account created, unclaimed, no password | 201 | active |
| Known email: a confirmation email goes to the address on file | 201 | pending_consent |
| A revoked link of this person: reopened, the person confirms again | 201 | pending_consent |
| The same ref and email again (a retry). A pending link is emailed again, at most once per 10 minutes | 200 | unchanged |
A RekberPay staff account, or a suspended or closing one, is handled exactly like any known email: the same pending_consent request, the same answers to a repeat POST and to GET, the same email limits. The request is simply never emailed and can never be accepted, so it stays pending_consent. Nothing tells you which accounts are staff or blocked.
Live email limits, so a partner can never use RekberPay to flood someone's inbox:
A link gets at most 3 confirmation emails a day. You have one link per person, so in practice you can send one person 3 a day, and other partners never use up your limit. One address gets at most 20 a day from all partners together. Calls refused past your own limit do not count toward the shared 20. Past any limit the call answers
429 MAIL_LIMIT_REACHEDwith aRetry-Afterheader (seconds until 00:00 UTC). A new request refused this way is not stored.When the person declined your request, or removed you from their connected partners, you cannot ask again for 7 days:
429 USER_DECLINED_RECENTLYwithRetry-After.The sandbox sends no email, so these limits do not apply there.
Auth: A workspace API key (
Authorization: Bearer rpe_...) sent from your server, or a dashboard session. Personal API keys and app sessions get403 KEY_NOT_ALLOWED.Scope:
users, andwriteon the key.Rate limit: 30 a minute per key, plus the shared 300 a minute of the member who made the key.
Idempotency: Required with a workspace key: send
Idempotency-Key(UUID v4), or the call answers400 IDEMPOTENCY_KEY_REQUIRED. A retry with the same key and body returns the first answer withIdempotent-Replay: true.
Body (JSON)
| Name | Type | Required | Description |
|---|---|---|---|
partner_user_ref | string | Yes | Your id for this person: 1 to 128 letters, digits or _ . : @ + = - |
email | string | Yes | At most 254 characters. In the sandbox it must be on example.com (or a subdomain), example.test, or a .test or .example domain |
full_name | string | Yes | 1 to 120 characters, without < or >. Ignored for a known email |
phone | string | null | No | 6 to 32 digits, spaces, +, -, dots or brackets |
In the sandbox no email is sent: a pending_consent answer adds connect_url, the link the person would get.
Example request
curl -X POST "https://api.rekberpay.com/v1/enterprise/platform/users" \
-H "Authorization: Bearer $REKBERPAY_WORKSPACE_KEY" \
-H "Idempotency-Key: 6f1d2c3b-4a5e-4f60-8a7b-1c2d3e4f5a60" \
-H "Content-Type: application/json" \
-d '{
"partner_user_ref": "seller-2001",
"email": "[email protected]",
"full_name": "Budi Santoso",
"phone": "+6281234567890"
}'const res = await fetch('https://api.rekberpay.com/v1/enterprise/platform/users', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.REKBERPAY_WORKSPACE_KEY}`,
'Content-Type': 'application/json',
'Idempotency-Key': '6f1d2c3b-4a5e-4f60-8a7b-1c2d3e4f5a60',
},
body: JSON.stringify({
partner_user_ref: "seller-2001",
email: "[email protected]",
full_name: "Budi Santoso",
phone: "+6281234567890",
}),
});
const data = await res.json();import os
import requests
resp = requests.post(
"https://api.rekberpay.com/v1/enterprise/platform/users",
headers={
"Authorization": f"Bearer {os.environ['REKBERPAY_WORKSPACE_KEY']}",
"Idempotency-Key": "6f1d2c3b-4a5e-4f60-8a7b-1c2d3e4f5a60",
},
json={
"partner_user_ref": "seller-2001",
"email": "[email protected]",
"full_name": "Budi Santoso",
"phone": "+6281234567890",
},
timeout=30,
)
print(resp.status_code, resp.json())<?php
$ch = curl_init('https://api.rekberpay.com/v1/enterprise/platform/users');
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('REKBERPAY_WORKSPACE_KEY'),
'Content-Type: application/json',
'Idempotency-Key: 6f1d2c3b-4a5e-4f60-8a7b-1c2d3e4f5a60',
],
CURLOPT_POSTFIELDS => json_encode([
'partner_user_ref' => 'seller-2001',
'email' => '[email protected]',
'full_name' => 'Budi Santoso',
'phone' => '+6281234567890',
]),
]);
$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": "7c1e2d3f-4a5b-4c6d-8e7f-901a2b3c4d5e",
"partner_user_ref": "seller-2001",
"link_status": "active",
"origin": "created",
"claimed": false,
"kyc_status": "none",
"balance_scope": "partner_funds",
"limits": {
"capped": true,
"kyc_status": "none",
"per_escrow_max": 10000000,
"daily_max": 25,
"lifetime_max": 25000000,
"max_order_now": 10000000
},
"email_masked": "b***@example.com"
}
}| Field | Meaning |
|---|---|
id | The link id |
link_status | pending_consent, active or revoked |
origin | created (you made the account) or linked (the person had one) |
claimed | true when the person controls the account: always for a linked account, and once a created account is claimed. null unless the link is active |
kyc_status | none, pending, verified or rejected. null unless the link is active |
balance_scope | partner_funds, or full_balance when the person let you manage their whole balance |
limits | The non-KYC limits: per_escrow_max rupiah per order, daily_max orders per 24 hours, lifetime_max rupiah in total, and max_order_now, the largest order total that fits right now (0 when the person used up their 24 hour order count). The person's own usage is never shown, because it includes their activity outside your platform. { "capped": false } once verified. null unless the link is active |
email_masked | The email on file, masked. null unless the link is active |
Errors
| Status | Code | Cause |
|---|---|---|
400 | INVALID_BODY | A field is missing or not valid |
409 | REF_IN_USE | This partner_user_ref is linked to a different email |
409 | USER_ALREADY_LINKED | This person is already linked to you under another ref |
422 | USER_NOT_AVAILABLE | RekberPay ended your link with this person, so it cannot be reopened. They can contact RekberPay support |
422 | SANDBOX_EMAIL_REQUIRED | A Sandbox key sent an email outside the test domains |
429 | MAIL_LIMIT_REACHED | Live only: this link or this address already got as many emails as allowed today. Wait Retry-After seconds |
429 | USER_DECLINED_RECENTLY | Live only: the person declined or ended the link less than 7 days ago. Wait Retry-After seconds |
| Plus the errors every Platform route can answer |
Get a user
Reads one link in any status. Personal fields are filled only while the link is active.
- Auth: A workspace API key (
Authorization: Bearer rpe_...) sent from your server, or a dashboard session. Personal API keys and app sessions get403 KEY_NOT_ALLOWED. - Scope:
users. - Rate limit: 120 a minute per key, plus the shared 300 a minute of the member who made the key.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
ref | string | Yes | Your partner_user_ref |
Example request
curl "https://api.rekberpay.com/v1/enterprise/platform/users/seller-2001" \
-H "Authorization: Bearer $REKBERPAY_WORKSPACE_KEY"const res = await fetch('https://api.rekberpay.com/v1/enterprise/platform/users/seller-2001', {
headers: {
Authorization: `Bearer ${process.env.REKBERPAY_WORKSPACE_KEY}`,
},
});
const data = await res.json();import os
import requests
resp = requests.get(
"https://api.rekberpay.com/v1/enterprise/platform/users/seller-2001",
headers={
"Authorization": f"Bearer {os.environ['REKBERPAY_WORKSPACE_KEY']}",
},
timeout=30,
)
print(resp.status_code, resp.json())<?php
$ch = curl_init('https://api.rekberpay.com/v1/enterprise/platform/users/seller-2001');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('REKBERPAY_WORKSPACE_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": "7c1e2d3f-4a5b-4c6d-8e7f-901a2b3c4d5e",
"partner_user_ref": "seller-2001",
"link_status": "active",
"origin": "created",
"claimed": false,
"kyc_status": "none",
"balance_scope": "partner_funds",
"limits": {
"capped": true,
"kyc_status": "none",
"per_escrow_max": 10000000,
"daily_max": 25,
"lifetime_max": 25000000,
"max_order_now": 10000000
},
"email_masked": "b***@example.com"
}
}Errors
| Status | Code | Cause |
|---|---|---|
404 | NOT_FOUND | No link with this ref in your workspace |
| Plus the errors every Platform route can answer |
Update a user
Changes the name, phone or email of an account you created that is not claimed yet. Name and phone change at once. An email change sends a confirmation link (24 hours) to the new address and applies only when it is opened. In live mode a user gets at most 3 email changes a day, you can send one new address at most 3 confirmation emails a day, and that address gets at most 10 a day from all partners together (429 MAIL_LIMIT_REACHED with Retry-After).
- Auth: A workspace API key (
Authorization: Bearer rpe_...) sent from your server, or a dashboard session. Personal API keys and app sessions get403 KEY_NOT_ALLOWED. - Scope:
users, andwriteon the key. - Rate limit: 30 a minute per key, plus the shared 300 a minute of the member who made the key.
- Idempotency: Required with a workspace key: send
Idempotency-Key(UUID v4), or the call answers400 IDEMPOTENCY_KEY_REQUIRED. A retry with the same key and body returns the first answer withIdempotent-Replay: true.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
ref | string | Yes | Your partner_user_ref |
Body (JSON)
| Name | Type | Required | Description |
|---|---|---|---|
full_name | string | No | 1 to 120 characters. Refused once the name is locked (KYC approved, or a balance, a bank account or a paid order with any partner) |
phone | string | null | No | As on create. null removes it |
email | string | No | The new address. Answers "email_change": "verification_sent" whether or not the address is free |
Send at least one field. In the sandbox an email change answer adds email_change_url instead of sending the email.
Example request
curl -X PATCH "https://api.rekberpay.com/v1/enterprise/platform/users/seller-2001" \
-H "Authorization: Bearer $REKBERPAY_WORKSPACE_KEY" \
-H "Idempotency-Key: 0a1b2c3d-4e5f-4a6b-8c7d-8e9f0a1b2c3d" \
-H "Content-Type: application/json" \
-d '{
"phone": "+6281299998888",
"email": "[email protected]"
}'const res = await fetch('https://api.rekberpay.com/v1/enterprise/platform/users/seller-2001', {
method: 'PATCH',
headers: {
Authorization: `Bearer ${process.env.REKBERPAY_WORKSPACE_KEY}`,
'Content-Type': 'application/json',
'Idempotency-Key': '0a1b2c3d-4e5f-4a6b-8c7d-8e9f0a1b2c3d',
},
body: JSON.stringify({
phone: "+6281299998888",
email: "[email protected]",
}),
});
const data = await res.json();import os
import requests
resp = requests.patch(
"https://api.rekberpay.com/v1/enterprise/platform/users/seller-2001",
headers={
"Authorization": f"Bearer {os.environ['REKBERPAY_WORKSPACE_KEY']}",
"Idempotency-Key": "0a1b2c3d-4e5f-4a6b-8c7d-8e9f0a1b2c3d",
},
json={
"phone": "+6281299998888",
"email": "[email protected]",
},
timeout=30,
)
print(resp.status_code, resp.json())<?php
$ch = curl_init('https://api.rekberpay.com/v1/enterprise/platform/users/seller-2001');
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'PATCH',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('REKBERPAY_WORKSPACE_KEY'),
'Content-Type: application/json',
'Idempotency-Key: 0a1b2c3d-4e5f-4a6b-8c7d-8e9f0a1b2c3d',
],
CURLOPT_POSTFIELDS => json_encode([
'phone' => '+6281299998888',
'email' => '[email protected]',
]),
]);
$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": "7c1e2d3f-4a5b-4c6d-8e7f-901a2b3c4d5e",
"partner_user_ref": "seller-2001",
"link_status": "active",
"origin": "created",
"claimed": false,
"kyc_status": "none",
"balance_scope": "partner_funds",
"limits": {
"capped": true,
"kyc_status": "none",
"per_escrow_max": 10000000,
"daily_max": 25,
"lifetime_max": 25000000,
"max_order_now": 10000000
},
"email_masked": "b***@example.com",
"email_change": "verification_sent"
}
}Errors
| Status | Code | Cause |
|---|---|---|
400 | INVALID_BODY | No field sent, or a field is not valid |
404 | NOT_FOUND | No active link with this ref |
409 | USER_MANAGED_BY_OWNER | The person claimed the account, or it was theirs before they linked |
409 | NAME_LOCKED | The name is locked: KYC approved, or a partner balance, a payout bank account or a paid order exists |
422 | SANDBOX_EMAIL_REQUIRED | A Sandbox key sent a new email outside the test domains |
429 | MAIL_LIMIT_REACHED | Live only: email changes for this user, or your confirmations to this new address, reached 3 today, or that address reached 10 today from all partners. Nothing in the request was saved. Wait Retry-After seconds |
| Plus the errors every Platform route can answer |
Unlink a user
Revokes the link. The account and its money stay with the person. To link again, send Create or link a user with the same email; the person must confirm again.
- Auth: A workspace API key (
Authorization: Bearer rpe_...) sent from your server, or a dashboard session. Personal API keys and app sessions get403 KEY_NOT_ALLOWED. - Scope:
users, andwriteon the key. - Rate limit: 30 a minute per key, plus the shared 300 a minute of the member who made the key.
- Idempotency: Required with a workspace key: send
Idempotency-Key(UUID v4), or the call answers400 IDEMPOTENCY_KEY_REQUIRED. A retry with the same key and body returns the first answer withIdempotent-Replay: true.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
ref | string | Yes | Your partner_user_ref |
Example request
curl -X DELETE "https://api.rekberpay.com/v1/enterprise/platform/users/seller-2001" \
-H "Authorization: Bearer $REKBERPAY_WORKSPACE_KEY" \
-H "Idempotency-Key: 1b2c3d4e-5f60-4b7c-8d8e-9f0a1b2c3d4e"const res = await fetch('https://api.rekberpay.com/v1/enterprise/platform/users/seller-2001', {
method: 'DELETE',
headers: {
Authorization: `Bearer ${process.env.REKBERPAY_WORKSPACE_KEY}`,
'Idempotency-Key': '1b2c3d4e-5f60-4b7c-8d8e-9f0a1b2c3d4e',
},
});
const data = await res.json();import os
import requests
resp = requests.delete(
"https://api.rekberpay.com/v1/enterprise/platform/users/seller-2001",
headers={
"Authorization": f"Bearer {os.environ['REKBERPAY_WORKSPACE_KEY']}",
"Idempotency-Key": "1b2c3d4e-5f60-4b7c-8d8e-9f0a1b2c3d4e",
},
timeout=30,
)
print(resp.status_code, resp.json())<?php
$ch = curl_init('https://api.rekberpay.com/v1/enterprise/platform/users/seller-2001');
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'DELETE',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('REKBERPAY_WORKSPACE_KEY'),
'Idempotency-Key: 1b2c3d4e-5f60-4b7c-8d8e-9f0a1b2c3d4e',
],
]);
$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": "7c1e2d3f-4a5b-4c6d-8e7f-901a2b3c4d5e",
"partner_user_ref": "seller-2001",
"link_status": "revoked",
"origin": "created",
"claimed": null,
"kyc_status": null,
"balance_scope": "partner_funds",
"limits": null,
"email_masked": null
}
}Errors
| Status | Code | Cause |
|---|---|---|
404 | NOT_FOUND | No active link with this ref, or the ref is not yours |
| Plus the errors every Platform route can answer |
Create a claim link
Lets the person take over an account you created. Either way they still need the 6 digit code RekberPay emails to the address on file.
- Auth: A workspace API key (
Authorization: Bearer rpe_...) sent from your server, or a dashboard session. Personal API keys and app sessions get403 KEY_NOT_ALLOWED. - Scope:
users, andwriteon the key. - Rate limit: 30 a minute per key, plus the shared 300 a minute of the member who made the key.
- Idempotency: Required with a workspace key: send
Idempotency-Key(UUID v4), or the call answers400 IDEMPOTENCY_KEY_REQUIRED. A retry with the same key and body returns the first answer withIdempotent-Replay: true.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
ref | string | Yes | Your partner_user_ref |
Body (JSON)
| Name | Type | Required | Description |
|---|---|---|---|
delivery | string | Yes | email: RekberPay emails a 24 hour link (202, no link in the answer; at most one email per 2 minutes and 10 a day per account, the same answer when throttled). link: you get a single use 30 minute link (200) |
In the sandbox no email is sent: email answers 202 with a 30 minute claim_url.
A sandbox claim is a simulation. The claim page shows the code instead of emailing it, and finishing it answers { "claimed": true, "sandbox": true }. From then on the user reads claimed: true and you can no longer change the account, as after a real claim. But no password is kept and no session opens: a sandbox test account can never sign in to RekberPay, and a live link can never claim it later. A sandbox link you reopened after an unlink is claimed and confirmed in one step through its connect_url.
Example request
curl -X POST "https://api.rekberpay.com/v1/enterprise/platform/users/seller-2001/claim-link" \
-H "Authorization: Bearer $REKBERPAY_WORKSPACE_KEY" \
-H "Idempotency-Key: 2c3d4e5f-6071-4c8d-9e9f-0a1b2c3d4e5f" \
-H "Content-Type: application/json" \
-d '{
"delivery": "link"
}'const res = await fetch('https://api.rekberpay.com/v1/enterprise/platform/users/seller-2001/claim-link', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.REKBERPAY_WORKSPACE_KEY}`,
'Content-Type': 'application/json',
'Idempotency-Key': '2c3d4e5f-6071-4c8d-9e9f-0a1b2c3d4e5f',
},
body: JSON.stringify({
delivery: "link",
}),
});
const data = await res.json();import os
import requests
resp = requests.post(
"https://api.rekberpay.com/v1/enterprise/platform/users/seller-2001/claim-link",
headers={
"Authorization": f"Bearer {os.environ['REKBERPAY_WORKSPACE_KEY']}",
"Idempotency-Key": "2c3d4e5f-6071-4c8d-9e9f-0a1b2c3d4e5f",
},
json={
"delivery": "link",
},
timeout=30,
)
print(resp.status_code, resp.json())<?php
$ch = curl_init('https://api.rekberpay.com/v1/enterprise/platform/users/seller-2001/claim-link');
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('REKBERPAY_WORKSPACE_KEY'),
'Content-Type: application/json',
'Idempotency-Key: 2c3d4e5f-6071-4c8d-9e9f-0a1b2c3d4e5f',
],
CURLOPT_POSTFIELDS => json_encode([
'delivery' => 'link',
]),
]);
$response = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
curl_close($ch);Example response
200 OK
{
"success": true,
"data": {
"url": "https://rekberpay.com/claim/rpt.eyJwIjoiY2xhaW0iLCJ4IjoxfQ.Hk2mQ9vT4xR7bN1cW8pL5sD0fJ3gA6eY2uI9oZ4tK1q",
"expires_at": "2026-10-21T08:30:00.000Z"
}
}Errors
| Status | Code | Cause |
|---|---|---|
400 | INVALID_BODY | delivery is missing or not email or link |
404 | NOT_FOUND | No active link with this ref, or the ref is not yours |
409 | USER_MANAGED_BY_OWNER | The account is already claimed, or was the person's own before they linked |
| Plus the errors every Platform route can answer |
Submit KYC
Submits the identity check of an account you created that is not claimed yet, as multipart/form-data. RekberPay's team reviews it. See KYC through the API.
- Auth: A workspace API key (
Authorization: Bearer rpe_...) sent from your server, or a dashboard session. Personal API keys and app sessions get403 KEY_NOT_ALLOWED. - Scope:
kyc, andwriteon the key. - Rate limit: 30 a minute per key, plus the shared 300 a minute of the member who made the key.
- Idempotency: Required. The key is bound to the path, not to the form: a reused key replays the first answer and the new form is not processed. Use a new key for every upload.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
ref | string | Yes | Your partner_user_ref |
Body (multipart/form-data)
| Name | Type | Required | Description |
|---|---|---|---|
kyc_id_type | text | Yes | ktp, sim or passport |
kyc_full_name_ktp | text | Yes | The name on the ID, up to 120 characters. Must match a locked name |
kyc_nik | text | Yes | The 16 digit NIK. In the sandbox a test NIK that ends in 0000 |
kyc_entity_name | text | No | A foundation or company name, up to 180 characters |
kyc_whatsapp | text | Yes | 08... or +62..., 8 to 20 characters |
kyc_province | text | Yes | One of the 38 province names, for example DKI Jakarta |
kyc_city | text | Yes | Up to 120 characters |
kyc_district | text | Yes | Up to 120 characters |
kyc_subdistrict | text | Yes | Up to 120 characters |
kyc_street_address | text | Yes | Up to 500 characters |
kyc_bank_name | text | Yes | A bank or e-wallet code such as bca, mandiri, bri or gopay (the full list is in the KYC guide) |
kyc_account_number | text | Yes | 4 to 40 digits |
front | file | Yes | The ID card. JPG, PNG or WebP, up to 10 MB |
selfie | file | Yes | The person holding the ID. JPG, PNG or WebP, up to 10 MB |
Example request
curl -X POST "https://api.rekberpay.com/v1/enterprise/platform/users/seller-2001/kyc" \
-H "Authorization: Bearer $REKBERPAY_WORKSPACE_KEY" \
-H "Idempotency-Key: 3d4e5f60-7182-4d9e-8fa0-1b2c3d4e5f60" \
-F "kyc_id_type=ktp" \
-F "kyc_full_name_ktp=Budi Santoso" \
-F "kyc_nik=3171014501900000" \
-F "kyc_whatsapp=081234567890" \
-F "kyc_province=DKI Jakarta" \
-F "kyc_city=Jakarta Selatan" \
-F "kyc_district=Kebayoran Baru" \
-F "kyc_subdistrict=Senayan" \
-F "kyc_street_address=Jl. Asia Afrika No. 8" \
-F "kyc_bank_name=bca" \
-F "kyc_account_number=1234567890" \
-F "[email protected]" \
-F "[email protected]"import { readFile } from 'node:fs/promises';
const form = new FormData();
form.append("kyc_id_type", "ktp");
form.append("kyc_full_name_ktp", "Budi Santoso");
form.append("kyc_nik", "3171014501900000");
form.append("kyc_whatsapp", "081234567890");
form.append("kyc_province", "DKI Jakarta");
form.append("kyc_city", "Jakarta Selatan");
form.append("kyc_district", "Kebayoran Baru");
form.append("kyc_subdistrict", "Senayan");
form.append("kyc_street_address", "Jl. Asia Afrika No. 8");
form.append("kyc_bank_name", "bca");
form.append("kyc_account_number", "1234567890");
form.append("front", new Blob([await readFile("test-ktp.jpg")], { type: "image/jpeg" }), "test-ktp.jpg");
form.append("selfie", new Blob([await readFile("test-selfie.jpg")], { type: "image/jpeg" }), "test-selfie.jpg");
const res = await fetch('https://api.rekberpay.com/v1/enterprise/platform/users/seller-2001/kyc', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.REKBERPAY_WORKSPACE_KEY}`,
'Idempotency-Key': '3d4e5f60-7182-4d9e-8fa0-1b2c3d4e5f60',
},
body: form,
});
const data = await res.json();import os
import requests
resp = requests.post(
"https://api.rekberpay.com/v1/enterprise/platform/users/seller-2001/kyc",
headers={
"Authorization": f"Bearer {os.environ['REKBERPAY_WORKSPACE_KEY']}",
"Idempotency-Key": "3d4e5f60-7182-4d9e-8fa0-1b2c3d4e5f60",
},
data={
"kyc_id_type": "ktp",
"kyc_full_name_ktp": "Budi Santoso",
"kyc_nik": "3171014501900000",
"kyc_whatsapp": "081234567890",
"kyc_province": "DKI Jakarta",
"kyc_city": "Jakarta Selatan",
"kyc_district": "Kebayoran Baru",
"kyc_subdistrict": "Senayan",
"kyc_street_address": "Jl. Asia Afrika No. 8",
"kyc_bank_name": "bca",
"kyc_account_number": "1234567890",
},
files=[
("front", ("test-ktp.jpg", open("test-ktp.jpg", "rb"), "image/jpeg")),
("selfie", ("test-selfie.jpg", open("test-selfie.jpg", "rb"), "image/jpeg")),
],
timeout=30,
)
print(resp.status_code, resp.json())<?php
$ch = curl_init('https://api.rekberpay.com/v1/enterprise/platform/users/seller-2001/kyc');
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('REKBERPAY_WORKSPACE_KEY'),
'Idempotency-Key: 3d4e5f60-7182-4d9e-8fa0-1b2c3d4e5f60',
],
CURLOPT_POSTFIELDS => [
'kyc_id_type' => 'ktp',
'kyc_full_name_ktp' => 'Budi Santoso',
'kyc_nik' => '3171014501900000',
'kyc_whatsapp' => '081234567890',
'kyc_province' => 'DKI Jakarta',
'kyc_city' => 'Jakarta Selatan',
'kyc_district' => 'Kebayoran Baru',
'kyc_subdistrict' => 'Senayan',
'kyc_street_address' => 'Jl. Asia Afrika No. 8',
'kyc_bank_name' => 'bca',
'kyc_account_number' => '1234567890',
'front' => new CURLFile('test-ktp.jpg', 'image/jpeg'),
'selfie' => new CURLFile('test-selfie.jpg', 'image/jpeg'),
],
]);
$response = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
curl_close($ch);Example response
202 Accepted
{
"success": true,
"data": {
"partner_user_ref": "seller-2001",
"kyc_status": "pending"
}
}You get platform.user.kyc_updated when the review ends.
Errors
| Status | Code | Cause |
|---|---|---|
400 | INVALID_BODY | Not multipart, a photo is missing, or a field is not valid. details.fields names the fields, never their values |
400 | INVALID_NIK | kyc_nik is not a valid NIK |
404 | NOT_FOUND | No active link with this ref, or the ref is not yours |
409 | USER_MANAGED_BY_OWNER | The person claimed the account, or it was theirs before they linked |
409 | KYC_LOCKED | A KYC is already pending or verified |
409 | NIK_IN_USE | This NIK already verifies another RekberPay account |
413 | FILE_TOO_LARGE | A photo is over 10 MB |
400 | TYPE_NOT_ALLOWED | A photo is not JPG, PNG or WebP |
400 | TYPE_MISMATCH | The content of a photo does not match its type or extension |
400 | IMAGE_DECODE_FAILED | A photo is damaged or not a real image |
429 | QUOTA_EXCEEDED | The person reached the daily upload limit for KYC photos |
500 | STORAGE_FAILED | The photo could not be stored. Nothing was submitted. Retry with a new Idempotency-Key |
422 | NAME_MISMATCH | The name is locked and kyc_full_name_ktp does not match it |
422 | SANDBOX_TEST_NIK_REQUIRED | A sandbox KYC must use a test NIK that ends in 0000 |
422 | USER_NOT_AVAILABLE | The account is suspended or closing |
429 | KYC_SUBMIT_LIMITED | Too many submissions for this person or your platform. Try again tomorrow |
| Plus the errors every Platform route can answer |
Get KYC status
The KYC status of an active link. Never the NIK, the name, the address, the bank or the photos.
- Auth: A workspace API key (
Authorization: Bearer rpe_...) sent from your server, or a dashboard session. Personal API keys and app sessions get403 KEY_NOT_ALLOWED. - Scope:
kyc. - Rate limit: 120 a minute per key, plus the shared 300 a minute of the member who made the key.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
ref | string | Yes | Your partner_user_ref |
Example request
curl "https://api.rekberpay.com/v1/enterprise/platform/users/seller-2001/kyc" \
-H "Authorization: Bearer $REKBERPAY_WORKSPACE_KEY"const res = await fetch('https://api.rekberpay.com/v1/enterprise/platform/users/seller-2001/kyc', {
headers: {
Authorization: `Bearer ${process.env.REKBERPAY_WORKSPACE_KEY}`,
},
});
const data = await res.json();import os
import requests
resp = requests.get(
"https://api.rekberpay.com/v1/enterprise/platform/users/seller-2001/kyc",
headers={
"Authorization": f"Bearer {os.environ['REKBERPAY_WORKSPACE_KEY']}",
},
timeout=30,
)
print(resp.status_code, resp.json())<?php
$ch = curl_init('https://api.rekberpay.com/v1/enterprise/platform/users/seller-2001/kyc');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('REKBERPAY_WORKSPACE_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": {
"kyc_status": "verified",
"submitted_at": "2026-10-21T08:00:00.000Z",
"reviewed_at": "2026-10-21T10:15:00.000Z",
"rejection_reason": null
}
}rejection_reason is filled only for a rejected KYC of an account you manage.
Errors
| Status | Code | Cause |
|---|---|---|
404 | NOT_FOUND | No active link with this ref, or the ref is not yours |
| Plus the errors every Platform route can answer |
Simulate a KYC result (sandbox)
Settles a pending sandbox KYC without RekberPay staff and without email, and sends platform.user.kyc_updated. Answers 404 in live mode.
A sandbox KYC is a simulation of your test user only. It is kept apart from the RekberPay account itself, so an approval never makes the account KYC verified on RekberPay and never changes its name. No photo from a sandbox submit is stored.
- Auth: A workspace API key (
Authorization: Bearer rpe_...) sent from your server, or a dashboard session. Personal API keys and app sessions get403 KEY_NOT_ALLOWED. - Scope:
kyc, andwriteon the key. - Rate limit: 30 a minute per key, plus the shared 300 a minute of the member who made the key.
- Idempotency: Required with a workspace key: send
Idempotency-Key(UUID v4), or the call answers400 IDEMPOTENCY_KEY_REQUIRED. A retry with the same key and body returns the first answer withIdempotent-Replay: true.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
ref | string | Yes | Your partner_user_ref |
Body (JSON)
| Name | Type | Required | Description |
|---|---|---|---|
result | string | Yes | approved or rejected |
reason | string | No | 3 to 500 characters, without < or >. Shown as rejection_reason |
Example request
curl -X POST "https://api.rekberpay.com/v1/enterprise/platform/users/seller-2001/kyc/simulate" \
-H "Authorization: Bearer $REKBERPAY_WORKSPACE_KEY" \
-H "Idempotency-Key: 4e5f6071-8293-4eaf-90b1-2c3d4e5f6071" \
-H "Content-Type: application/json" \
-d '{
"result": "approved"
}'const res = await fetch('https://api.rekberpay.com/v1/enterprise/platform/users/seller-2001/kyc/simulate', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.REKBERPAY_WORKSPACE_KEY}`,
'Content-Type': 'application/json',
'Idempotency-Key': '4e5f6071-8293-4eaf-90b1-2c3d4e5f6071',
},
body: JSON.stringify({
result: "approved",
}),
});
const data = await res.json();import os
import requests
resp = requests.post(
"https://api.rekberpay.com/v1/enterprise/platform/users/seller-2001/kyc/simulate",
headers={
"Authorization": f"Bearer {os.environ['REKBERPAY_WORKSPACE_KEY']}",
"Idempotency-Key": "4e5f6071-8293-4eaf-90b1-2c3d4e5f6071",
},
json={
"result": "approved",
},
timeout=30,
)
print(resp.status_code, resp.json())<?php
$ch = curl_init('https://api.rekberpay.com/v1/enterprise/platform/users/seller-2001/kyc/simulate');
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('REKBERPAY_WORKSPACE_KEY'),
'Content-Type: application/json',
'Idempotency-Key: 4e5f6071-8293-4eaf-90b1-2c3d4e5f6071',
],
CURLOPT_POSTFIELDS => json_encode([
'result' => 'approved',
]),
]);
$response = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
curl_close($ch);Example response
200 OK
{
"success": true,
"data": {
"kyc_status": "verified",
"submitted_at": "2026-10-21T08:00:00.000Z",
"reviewed_at": "2026-10-21T08:05:00.000Z",
"rejection_reason": null
}
}Errors
| Status | Code | Cause |
|---|---|---|
400 | INVALID_BODY | result is missing or not valid |
404 | NOT_FOUND | A Live request, or no active link with this ref |
409 | KYC_NOT_PENDING | There is no pending KYC to settle |
422 | NAME_MISMATCH | The name is locked and the KYC name does not match it |
| Plus the errors every Platform route can answer |
Create a deal
Opens a marketplace order between two active users of yours, with one or more milestones. Every milestone starts awaiting_payment. The fee is one fee per deal, spread over the milestones.
- Auth: A workspace API key (
Authorization: Bearer rpe_...) sent from your server, or a dashboard session. Personal API keys and app sessions get403 KEY_NOT_ALLOWED. - Scope:
orders, andwriteon the key. - Rate limit: 60 a minute per key, plus the shared 300 a minute of the member who made the key.
- Idempotency: Required with a workspace key: send
Idempotency-Key(UUID v4), or the call answers400 IDEMPOTENCY_KEY_REQUIRED. A retry with the same key and body returns the first answer withIdempotent-Replay: true.
Body (JSON)
| Name | Type | Required | Description |
|---|---|---|---|
buyer_ref | string | Yes | The buyer's partner_user_ref. Must be an active link |
seller_ref | string | Yes | The seller's partner_user_ref. Another person than the buyer |
title | string | Yes | 2 to 180 characters |
partner_order_ref | string | Yes | Your order id, 1 to 128 characters. Unique in your workspace |
milestones | array | Yes | 1 to 20 milestones, each { title, amount }: a title of 2 to 180 characters and a whole rupiah amount from 10,000 to 50,000,000,000 |
fee_bearer | string | No | buyer, seller or split. Default: your default_fee_bearer |
fee_split_buyer_pct | integer | No | 0 to 100, the buyer's share with split only. Default: your default_split_buyer_pct |
metadata | object | No | Up to 20 string values, keys of 1 to 40 letters, digits, ., - or _, values up to 500 characters |
RekberPay checks, in one step: both refs are active links of yours, the buyer and the seller are different people and in good standing, both are within the non-KYC limits (one budget across RekberPay and every partner), and the deal fits your daily cap.
Example request
curl -X POST "https://api.rekberpay.com/v1/enterprise/platform/deals" \
-H "Authorization: Bearer $REKBERPAY_WORKSPACE_KEY" \
-H "Idempotency-Key: 5f607182-93a4-4fb0-a1c2-3d4e5f607182" \
-H "Content-Type: application/json" \
-d '{
"buyer_ref": "buyer-1001",
"seller_ref": "seller-2001",
"title": "Custom teak desk",
"partner_order_ref": "ORD-55020",
"milestones": [
{
"title": "Deposit",
"amount": 1000000
},
{
"title": "Final payment",
"amount": 500000
}
],
"fee_bearer": "split",
"fee_split_buyer_pct": 50,
"metadata": {
"listing_id": "L-88123"
}
}'const res = await fetch('https://api.rekberpay.com/v1/enterprise/platform/deals', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.REKBERPAY_WORKSPACE_KEY}`,
'Content-Type': 'application/json',
'Idempotency-Key': '5f607182-93a4-4fb0-a1c2-3d4e5f607182',
},
body: JSON.stringify({
buyer_ref: "buyer-1001",
seller_ref: "seller-2001",
title: "Custom teak desk",
partner_order_ref: "ORD-55020",
milestones: [
{
title: "Deposit",
amount: 1000000,
},
{
title: "Final payment",
amount: 500000,
},
],
fee_bearer: "split",
fee_split_buyer_pct: 50,
metadata: {
listing_id: "L-88123",
},
}),
});
const data = await res.json();import os
import requests
resp = requests.post(
"https://api.rekberpay.com/v1/enterprise/platform/deals",
headers={
"Authorization": f"Bearer {os.environ['REKBERPAY_WORKSPACE_KEY']}",
"Idempotency-Key": "5f607182-93a4-4fb0-a1c2-3d4e5f607182",
},
json={
"buyer_ref": "buyer-1001",
"seller_ref": "seller-2001",
"title": "Custom teak desk",
"partner_order_ref": "ORD-55020",
"milestones": [
{
"title": "Deposit",
"amount": 1000000,
},
{
"title": "Final payment",
"amount": 500000,
},
],
"fee_bearer": "split",
"fee_split_buyer_pct": 50,
"metadata": {
"listing_id": "L-88123",
},
},
timeout=30,
)
print(resp.status_code, resp.json())<?php
$ch = curl_init('https://api.rekberpay.com/v1/enterprise/platform/deals');
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('REKBERPAY_WORKSPACE_KEY'),
'Content-Type: application/json',
'Idempotency-Key: 5f607182-93a4-4fb0-a1c2-3d4e5f607182',
],
CURLOPT_POSTFIELDS => json_encode([
'buyer_ref' => 'buyer-1001',
'seller_ref' => 'seller-2001',
'title' => 'Custom teak desk',
'partner_order_ref' => 'ORD-55020',
'milestones' => [
[
'title' => 'Deposit',
'amount' => 1000000,
],
[
'title' => 'Final payment',
'amount' => 500000,
],
],
'fee_bearer' => 'split',
'fee_split_buyer_pct' => 50,
'metadata' => [
'listing_id' => 'L-88123',
],
]),
]);
$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": "3e4f5a6b-7c8d-4e9f-a0b1-c2d3e4f5a6b7",
"reference": "DEAL-2026-0042",
"partner_order_ref": "ORD-55020",
"title": "Custom teak desk",
"status": "awaiting_funding",
"is_test": true,
"buyer_ref": "buyer-1001",
"seller_ref": "seller-2001",
"currency": "IDR",
"total_amount": 1500000,
"funded_amount": 0,
"released_amount": 0,
"fee_bearer": "split",
"fee_split_buyer_pct": 50,
"platform_fee": 30000,
"buyer_fee": 15000,
"seller_fee": 15000,
"metadata": {
"listing_id": "L-88123"
},
"milestones": [
{
"id": "4f5a6b7c-8d9e-4fa0-b1c2-d3e4f5a6b7c8",
"sequence": 1,
"title": "Deposit",
"status": "awaiting_payment",
"amount": 1000000,
"buyer_fee": 10000,
"seller_fee": 10000,
"funding_amount": 1010000,
"payout_amount": 990000,
"refund_amount": null,
"paid_at": null,
"courier": null,
"tracking_number": null,
"shipped_at": null,
"received_at": null,
"received_by": null,
"auto_release_at": null,
"release_at": null,
"released_at": null,
"extend_count": 0,
"disputed_at": null
},
{
"id": "9a0b1c2d-3e4f-4a5b-8c6d-7e8f9a0b1c2d",
"sequence": 2,
"title": "Final payment",
"status": "awaiting_payment",
"amount": 500000,
"buyer_fee": 5000,
"seller_fee": 5000,
"funding_amount": 505000,
"payout_amount": 495000,
"refund_amount": null,
"paid_at": null,
"courier": null,
"tracking_number": null,
"shipped_at": null,
"received_at": null,
"received_by": null,
"auto_release_at": null,
"release_at": null,
"released_at": null,
"extend_count": 0,
"disputed_at": null
}
],
"created_at": "2026-10-21T08:00:00.000Z",
"updated_at": "2026-10-21T08:00:00.000Z"
}
}Deal status: awaiting_funding, funded (every open milestone paid), in_progress, disputed (any milestone disputed), completed (every milestone finished, at least one released) or cancelled. Milestone status: awaiting_payment, paid, shipped, received, release_pending, released, disputed, refunded or cancelled.
| Milestone field | Meaning |
|---|---|
funding_amount | What the buyer pays: amount plus buyer_fee (the payment method fee comes on top at checkout) |
payout_amount | What the seller receives on release: amount minus seller_fee, or the seller part of a split |
refund_amount | What a dispute sent back to the buyer, or null |
received_by | buyer, partner, auto or admin once it is released or pending release |
auto_release_at, release_at | When the automatic release or your pending confirm fires |
Errors
| Status | Code | Cause |
|---|---|---|
400 | INVALID_BODY | A field is missing or not valid |
400 | INVALID_FEE_ALLOCATION | fee_split_buyer_pct without split, or a percent outside 0 to 100 |
404 | USER_NOT_FOUND | No active user with this buyer_ref or seller_ref (see field) |
409 | DUPLICATE_ORDER_REF | A deal with this partner_order_ref already exists |
409 | DAILY_CAP_REACHED | The deal would pass your daily cap. Carries daily_cap_idr and used_today_idr |
409 | DEAL_CREATE_BUSY | Another deal is being created at the same moment. Retry |
422 | SAME_PARTY | The buyer and the seller are the same person |
422 | USER_NOT_AVAILABLE | The buyer or seller cannot take part in a new order (see field) |
422 | LIMIT_EXCEEDED | The buyer or seller cannot take a deal this size without KYC (reason NONKYC_LIMIT_AMOUNT), or their KYC status blocks it. Carries party and reason. The 24 hour count and the lifetime total are checked at checkout, not here |
422 | FEE_EXCEEDS_AMOUNT | A milestone is too small for the seller's share of the fee |
| Plus the errors every Platform route can answer |
Get a deal
One deal with all its milestones. Use it to check a status after a webhook.
- Auth: A workspace API key (
Authorization: Bearer rpe_...) sent from your server, or a dashboard session. Personal API keys and app sessions get403 KEY_NOT_ALLOWED. - Scope:
orders. - Rate limit: 120 a minute per key, plus the shared 300 a minute of the member who made the key.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
id | UUID | Yes | The deal id |
Example request
curl "https://api.rekberpay.com/v1/enterprise/platform/deals/3e4f5a6b-7c8d-4e9f-a0b1-c2d3e4f5a6b7" \
-H "Authorization: Bearer $REKBERPAY_WORKSPACE_KEY"const res = await fetch('https://api.rekberpay.com/v1/enterprise/platform/deals/3e4f5a6b-7c8d-4e9f-a0b1-c2d3e4f5a6b7', {
headers: {
Authorization: `Bearer ${process.env.REKBERPAY_WORKSPACE_KEY}`,
},
});
const data = await res.json();import os
import requests
resp = requests.get(
"https://api.rekberpay.com/v1/enterprise/platform/deals/3e4f5a6b-7c8d-4e9f-a0b1-c2d3e4f5a6b7",
headers={
"Authorization": f"Bearer {os.environ['REKBERPAY_WORKSPACE_KEY']}",
},
timeout=30,
)
print(resp.status_code, resp.json())<?php
$ch = curl_init('https://api.rekberpay.com/v1/enterprise/platform/deals/3e4f5a6b-7c8d-4e9f-a0b1-c2d3e4f5a6b7');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('REKBERPAY_WORKSPACE_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": "3e4f5a6b-7c8d-4e9f-a0b1-c2d3e4f5a6b7",
"reference": "DEAL-2026-0042",
"partner_order_ref": "ORD-55020",
"title": "Custom teak desk",
"status": "in_progress",
"is_test": true,
"buyer_ref": "buyer-1001",
"seller_ref": "seller-2001",
"currency": "IDR",
"total_amount": 1500000,
"funded_amount": 1000000,
"released_amount": 0,
"fee_bearer": "split",
"fee_split_buyer_pct": 50,
"platform_fee": 30000,
"buyer_fee": 15000,
"seller_fee": 15000,
"metadata": {
"listing_id": "L-88123"
},
"milestones": [
{
"id": "4f5a6b7c-8d9e-4fa0-b1c2-d3e4f5a6b7c8",
"sequence": 1,
"title": "Deposit",
"status": "shipped",
"amount": 1000000,
"buyer_fee": 10000,
"seller_fee": 10000,
"funding_amount": 1010000,
"payout_amount": 990000,
"refund_amount": null,
"paid_at": "2026-10-21T08:12:40.000Z",
"courier": "JNE",
"tracking_number": "JNE0012345678",
"shipped_at": "2026-10-21T09:00:00.000Z",
"received_at": null,
"received_by": null,
"auto_release_at": "2026-10-24T09:00:00.000Z",
"release_at": null,
"released_at": null,
"extend_count": 0,
"disputed_at": null
},
{
"id": "9a0b1c2d-3e4f-4a5b-8c6d-7e8f9a0b1c2d",
"sequence": 2,
"title": "Final payment",
"status": "awaiting_payment",
"amount": 500000,
"buyer_fee": 5000,
"seller_fee": 5000,
"funding_amount": 505000,
"payout_amount": 495000,
"refund_amount": null,
"paid_at": null,
"courier": null,
"tracking_number": null,
"shipped_at": null,
"received_at": null,
"received_by": null,
"auto_release_at": null,
"release_at": null,
"released_at": null,
"extend_count": 0,
"disputed_at": null
}
],
"created_at": "2026-10-21T08:00:00.000Z",
"updated_at": "2026-10-21T09:00:00.000Z"
}
}Errors
| Status | Code | Cause |
|---|---|---|
404 | NOT_FOUND | No such deal in your workspace |
| Plus the errors every Platform route can answer |
List deals
Your marketplace deals, newest first. Page with before: pass pagination.next_before from the previous page.
- Auth: A workspace API key (
Authorization: Bearer rpe_...) sent from your server, or a dashboard session. Personal API keys and app sessions get403 KEY_NOT_ALLOWED. - Scope:
orders. - Rate limit: 120 a minute per key, plus the shared 300 a minute of the member who made the key.
Query parameters
| Name | Type | Required | Description |
|---|---|---|---|
user | string | No | Only deals where this partner_user_ref is the buyer or the seller |
status | string | No | A deal status: awaiting_funding, funded, in_progress, disputed, completed or cancelled |
before | UUID | No | The id of the last deal of the previous page |
limit | integer | No | 1 to 50, default 20 |
Example request
curl "https://api.rekberpay.com/v1/enterprise/platform/deals?user=seller-2001&status=awaiting_funding&limit=20" \
-H "Authorization: Bearer $REKBERPAY_WORKSPACE_KEY"const res = await fetch('https://api.rekberpay.com/v1/enterprise/platform/deals?user=seller-2001&status=awaiting_funding&limit=20', {
headers: {
Authorization: `Bearer ${process.env.REKBERPAY_WORKSPACE_KEY}`,
},
});
const data = await res.json();import os
import requests
resp = requests.get(
"https://api.rekberpay.com/v1/enterprise/platform/deals?user=seller-2001&status=awaiting_funding&limit=20",
headers={
"Authorization": f"Bearer {os.environ['REKBERPAY_WORKSPACE_KEY']}",
},
timeout=30,
)
print(resp.status_code, resp.json())<?php
$ch = curl_init('https://api.rekberpay.com/v1/enterprise/platform/deals?user=seller-2001&status=awaiting_funding&limit=20');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('REKBERPAY_WORKSPACE_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": "3e4f5a6b-7c8d-4e9f-a0b1-c2d3e4f5a6b7",
"reference": "DEAL-2026-0042",
"partner_order_ref": "ORD-55020",
"title": "Custom teak desk",
"status": "awaiting_funding",
"is_test": true,
"buyer_ref": "buyer-1001",
"seller_ref": "seller-2001",
"currency": "IDR",
"total_amount": 1500000,
"funded_amount": 0,
"released_amount": 0,
"fee_bearer": "split",
"fee_split_buyer_pct": 50,
"platform_fee": 30000,
"buyer_fee": 15000,
"seller_fee": 15000,
"metadata": {
"listing_id": "L-88123"
},
"milestones": [
{
"id": "4f5a6b7c-8d9e-4fa0-b1c2-d3e4f5a6b7c8",
"sequence": 1,
"title": "Deposit",
"status": "awaiting_payment",
"amount": 1000000,
"buyer_fee": 10000,
"seller_fee": 10000,
"funding_amount": 1010000,
"payout_amount": 990000,
"refund_amount": null,
"paid_at": null,
"courier": null,
"tracking_number": null,
"shipped_at": null,
"received_at": null,
"received_by": null,
"auto_release_at": null,
"release_at": null,
"released_at": null,
"extend_count": 0,
"disputed_at": null
},
{
"id": "9a0b1c2d-3e4f-4a5b-8c6d-7e8f9a0b1c2d",
"sequence": 2,
"title": "Final payment",
"status": "awaiting_payment",
"amount": 500000,
"buyer_fee": 5000,
"seller_fee": 5000,
"funding_amount": 505000,
"payout_amount": 495000,
"refund_amount": null,
"paid_at": null,
"courier": null,
"tracking_number": null,
"shipped_at": null,
"received_at": null,
"received_by": null,
"auto_release_at": null,
"release_at": null,
"released_at": null,
"extend_count": 0,
"disputed_at": null
}
],
"created_at": "2026-10-21T08:00:00.000Z",
"updated_at": "2026-10-21T08:00:00.000Z"
}
],
"pagination": {
"limit": 20,
"has_more": false,
"next_before": null
}
}Errors
| Status | Code | Cause |
|---|---|---|
400 | INVALID_QUERY | A query value is not valid |
| Plus the errors every Platform route can answer |
Cancel a deal
Cancels a deal before any milestone is paid. Every unpaid milestone becomes cancelled. After a payment, open a dispute instead.
- Auth: A workspace API key (
Authorization: Bearer rpe_...) sent from your server, or a dashboard session. Personal API keys and app sessions get403 KEY_NOT_ALLOWED. - Scope:
orders, andwriteon the key. - Rate limit: 60 a minute per key, plus the shared 300 a minute of the member who made the key.
- Idempotency: Required with a workspace key: send
Idempotency-Key(UUID v4), or the call answers400 IDEMPOTENCY_KEY_REQUIRED. A retry with the same key and body returns the first answer withIdempotent-Replay: true.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
id | UUID | Yes | The deal id |
Example request
curl -X POST "https://api.rekberpay.com/v1/enterprise/platform/deals/3e4f5a6b-7c8d-4e9f-a0b1-c2d3e4f5a6b7/cancel" \
-H "Authorization: Bearer $REKBERPAY_WORKSPACE_KEY" \
-H "Idempotency-Key: 60718293-a4b5-40c1-b2d3-4e5f60718293"const res = await fetch('https://api.rekberpay.com/v1/enterprise/platform/deals/3e4f5a6b-7c8d-4e9f-a0b1-c2d3e4f5a6b7/cancel', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.REKBERPAY_WORKSPACE_KEY}`,
'Idempotency-Key': '60718293-a4b5-40c1-b2d3-4e5f60718293',
},
});
const data = await res.json();import os
import requests
resp = requests.post(
"https://api.rekberpay.com/v1/enterprise/platform/deals/3e4f5a6b-7c8d-4e9f-a0b1-c2d3e4f5a6b7/cancel",
headers={
"Authorization": f"Bearer {os.environ['REKBERPAY_WORKSPACE_KEY']}",
"Idempotency-Key": "60718293-a4b5-40c1-b2d3-4e5f60718293",
},
timeout=30,
)
print(resp.status_code, resp.json())<?php
$ch = curl_init('https://api.rekberpay.com/v1/enterprise/platform/deals/3e4f5a6b-7c8d-4e9f-a0b1-c2d3e4f5a6b7/cancel');
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('REKBERPAY_WORKSPACE_KEY'),
'Idempotency-Key: 60718293-a4b5-40c1-b2d3-4e5f60718293',
],
]);
$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": "3e4f5a6b-7c8d-4e9f-a0b1-c2d3e4f5a6b7",
"reference": "DEAL-2026-0042",
"partner_order_ref": "ORD-55020",
"title": "Custom teak desk",
"status": "cancelled",
"is_test": true,
"buyer_ref": "buyer-1001",
"seller_ref": "seller-2001",
"currency": "IDR",
"total_amount": 1500000,
"funded_amount": 0,
"released_amount": 0,
"fee_bearer": "split",
"fee_split_buyer_pct": 50,
"platform_fee": 0,
"buyer_fee": 0,
"seller_fee": 0,
"metadata": {
"listing_id": "L-88123"
},
"milestones": [
{
"id": "4f5a6b7c-8d9e-4fa0-b1c2-d3e4f5a6b7c8",
"sequence": 1,
"title": "Deposit",
"status": "cancelled",
"amount": 1000000,
"buyer_fee": 10000,
"seller_fee": 10000,
"funding_amount": 1010000,
"payout_amount": 990000,
"refund_amount": null,
"paid_at": null,
"courier": null,
"tracking_number": null,
"shipped_at": null,
"received_at": null,
"received_by": null,
"auto_release_at": null,
"release_at": null,
"released_at": null,
"extend_count": 0,
"disputed_at": null
},
{
"id": "9a0b1c2d-3e4f-4a5b-8c6d-7e8f9a0b1c2d",
"sequence": 2,
"title": "Final payment",
"status": "cancelled",
"amount": 500000,
"buyer_fee": 5000,
"seller_fee": 5000,
"funding_amount": 505000,
"payout_amount": 495000,
"refund_amount": null,
"paid_at": null,
"courier": null,
"tracking_number": null,
"shipped_at": null,
"received_at": null,
"received_by": null,
"auto_release_at": null,
"release_at": null,
"released_at": null,
"extend_count": 0,
"disputed_at": null
}
],
"created_at": "2026-10-21T08:00:00.000Z",
"updated_at": "2026-10-21T08:20:00.000Z"
}
}The deal's platform_fee, buyer_fee and seller_fee drop to 0, since no milestone is left to charge; each milestone keeps its own fee shares and total_amount stays the sum of every milestone. You get deal.cancelled.
Errors
| Status | Code | Cause |
|---|---|---|
404 | NOT_FOUND | No such deal in your workspace |
409 | INVALID_STATE | A milestone is already paid, or the deal is already cancelled |
409 | PAYMENT_IN_PROGRESS | A checkout payment can still land. Wait until it is confirmed or expires |
| Plus the errors every Platform route can answer |
Get a checkout link
A RekberPay checkout URL for one milestone, valid for 30 minutes. Send the buyer there; no sign-in is needed. See Checkout for buyers.
- Auth: A workspace API key (
Authorization: Bearer rpe_...) sent from your server, or a dashboard session. Personal API keys and app sessions get403 KEY_NOT_ALLOWED. - Scope:
orders, andwriteon the key. - Rate limit: 60 a minute per key, plus the shared 300 a minute of the member who made the key.
- Idempotency: Required with a workspace key: send
Idempotency-Key(UUID v4), or the call answers400 IDEMPOTENCY_KEY_REQUIRED. A retry with the same key and body returns the first answer withIdempotent-Replay: true.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
id | UUID | Yes | The deal id |
mid | UUID | Yes | The milestone id |
Example request
curl -X POST "https://api.rekberpay.com/v1/enterprise/platform/deals/3e4f5a6b-7c8d-4e9f-a0b1-c2d3e4f5a6b7/milestones/4f5a6b7c-8d9e-4fa0-b1c2-d3e4f5a6b7c8/checkout" \
-H "Authorization: Bearer $REKBERPAY_WORKSPACE_KEY" \
-H "Idempotency-Key: 718293a4-b5c6-41d2-83e4-5f60718293a4"const res = await fetch('https://api.rekberpay.com/v1/enterprise/platform/deals/3e4f5a6b-7c8d-4e9f-a0b1-c2d3e4f5a6b7/milestones/4f5a6b7c-8d9e-4fa0-b1c2-d3e4f5a6b7c8/checkout', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.REKBERPAY_WORKSPACE_KEY}`,
'Idempotency-Key': '718293a4-b5c6-41d2-83e4-5f60718293a4',
},
});
const data = await res.json();import os
import requests
resp = requests.post(
"https://api.rekberpay.com/v1/enterprise/platform/deals/3e4f5a6b-7c8d-4e9f-a0b1-c2d3e4f5a6b7/milestones/4f5a6b7c-8d9e-4fa0-b1c2-d3e4f5a6b7c8/checkout",
headers={
"Authorization": f"Bearer {os.environ['REKBERPAY_WORKSPACE_KEY']}",
"Idempotency-Key": "718293a4-b5c6-41d2-83e4-5f60718293a4",
},
timeout=30,
)
print(resp.status_code, resp.json())<?php
$ch = curl_init('https://api.rekberpay.com/v1/enterprise/platform/deals/3e4f5a6b-7c8d-4e9f-a0b1-c2d3e4f5a6b7/milestones/4f5a6b7c-8d9e-4fa0-b1c2-d3e4f5a6b7c8/checkout');
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('REKBERPAY_WORKSPACE_KEY'),
'Idempotency-Key: 718293a4-b5c6-41d2-83e4-5f60718293a4',
],
]);
$response = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
curl_close($ch);Example response
201 Created
{
"success": true,
"data": {
"url": "https://rekberpay.com/checkout/p/rpt.eyJwIjoiY2hlY2tvdXQiLCJ4IjoxfQ.q3J8vYc0bX2mN5tK9wE1rL7sA4dF6hG8jZ0pQ2uV4yI",
"expires_at": "2026-10-21T08:30:00.000Z"
}
}The link starts one payment. The buyer page answers 409 CHECKOUT_USED for a second payment with the same link, 409 CHECKOUT_CHANGED when the order changed after you made it, and 410 LINK_EXPIRED once it is dead. After a gateway payment the buyer lands on https://rekberpay.com/checkout/p/r/<payment_link_id>, a read-only status page kept for 30 days.
Errors
| Status | Code | Cause |
|---|---|---|
404 | NOT_FOUND | No such deal or milestone in your workspace |
409 | INVALID_STATE | The milestone is not waiting for payment |
409 | USER_NOT_AVAILABLE | The buyer is no longer linked to you |
422 | LIMIT_EXCEEDED | The buyer or the seller has no room left in the non-KYC limits for this milestone. Carries party and reason (NONKYC_LIMIT_AMOUNT, NONKYC_LIMIT_DAILY or NONKYC_LIMIT_LIFETIME) |
| Plus the errors every Platform route can answer |
A milestone counts toward the buyer's and the seller's non-KYC limits from checkout or payment, never when the deal is created, so an order nobody pays holds none of their limits. The buyer page checks the limits again when the buyer pays, and refuses a second payment while an earlier one on the milestone can still be paid (409 PAYMENT_IN_PROGRESS).
Simulate a payment (sandbox)
A sandbox order is paid with test money. The checkout page has a Simulasikan pembayaran button, or your server calls the buyer checkout API with the token (the part of the URL after /checkout/p/). No key is sent: the token is the only credential, and the call spends it like a real payment. It is limited to 20 calls a minute per IP address.
CHECKOUT_TOKEN=${CHECKOUT_URL##*/checkout/p/}
curl -X POST "https://api.rekberpay.com/v1/platform/checkout/$CHECKOUT_TOKEN/simulate"{
"success": true,
"data": {
"milestone_status": "paid",
"is_test": true
}
}You get milestone.paid. Its own answers:
| Status | Code | Cause |
|---|---|---|
404 | NOT_FOUND | A live order: only sandbox orders can be simulated |
409 | CHECKOUT_USED | This token already started a payment |
409 | CHECKOUT_CHANGED | The order changed after the link was made |
409 | INVALID_STATE | The milestone is not waiting for payment |
410 | LINK_EXPIRED | The token is invalid or expired |
423 | PLATFORM_FROZEN | RekberPay paused your Platform access |
429 | RATE_LIMITED | More than 20 calls a minute from this IP |
Cancel a milestone
Cancels one milestone that is still awaiting_payment, also when other milestones of the deal are paid. Use it when part of an order is called off. The deal status is derived again: it is completed once every other milestone is released or refunded (with at least one released), and cancelled when nothing else is left, which also sends deal.cancelled. There is no milestone event for this; read the answer, which is the deal.
- Auth: A workspace API key (
Authorization: Bearer rpe_...) sent from your server, or a dashboard session. Personal API keys and app sessions get403 KEY_NOT_ALLOWED. - Scope:
orders, andwriteon the key. - Rate limit: 60 a minute per key, plus the shared 300 a minute of the member who made the key.
- Idempotency: Required with a workspace key: send
Idempotency-Key(UUID v4), or the call answers400 IDEMPOTENCY_KEY_REQUIRED. A retry with the same key and body returns the first answer withIdempotent-Replay: true.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
id | UUID | Yes | The deal id |
mid | UUID | Yes | The milestone id |
Example request
curl -X POST "https://api.rekberpay.com/v1/enterprise/platform/deals/3e4f5a6b-7c8d-4e9f-a0b1-c2d3e4f5a6b7/milestones/9a0b1c2d-3e4f-4a5b-8c6d-7e8f9a0b1c2d/cancel" \
-H "Authorization: Bearer $REKBERPAY_WORKSPACE_KEY" \
-H "Idempotency-Key: 8293a4b5-c6d7-42e3-94f5-60718293a4b5"const res = await fetch('https://api.rekberpay.com/v1/enterprise/platform/deals/3e4f5a6b-7c8d-4e9f-a0b1-c2d3e4f5a6b7/milestones/9a0b1c2d-3e4f-4a5b-8c6d-7e8f9a0b1c2d/cancel', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.REKBERPAY_WORKSPACE_KEY}`,
'Idempotency-Key': '8293a4b5-c6d7-42e3-94f5-60718293a4b5',
},
});
const data = await res.json();import os
import requests
resp = requests.post(
"https://api.rekberpay.com/v1/enterprise/platform/deals/3e4f5a6b-7c8d-4e9f-a0b1-c2d3e4f5a6b7/milestones/9a0b1c2d-3e4f-4a5b-8c6d-7e8f9a0b1c2d/cancel",
headers={
"Authorization": f"Bearer {os.environ['REKBERPAY_WORKSPACE_KEY']}",
"Idempotency-Key": "8293a4b5-c6d7-42e3-94f5-60718293a4b5",
},
timeout=30,
)
print(resp.status_code, resp.json())<?php
$ch = curl_init('https://api.rekberpay.com/v1/enterprise/platform/deals/3e4f5a6b-7c8d-4e9f-a0b1-c2d3e4f5a6b7/milestones/9a0b1c2d-3e4f-4a5b-8c6d-7e8f9a0b1c2d/cancel');
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('REKBERPAY_WORKSPACE_KEY'),
'Idempotency-Key: 8293a4b5-c6d7-42e3-94f5-60718293a4b5',
],
]);
$response = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
curl_close($ch);Example response
200 OK. The deal as Get a deal shows it. Here the first milestone was released, so cancelling the second completes the deal:
{
"success": true,
"data": {
"id": "3e4f5a6b-7c8d-4e9f-a0b1-c2d3e4f5a6b7",
"reference": "DEAL-2026-0042",
"partner_order_ref": "ORD-55020",
"status": "completed",
"total_amount": 1500000,
"funded_amount": 1000000,
"released_amount": 1000000,
"milestones": [
{ "id": "4f5a6b7c-8d9e-4fa0-b1c2-d3e4f5a6b7c8", "sequence": 1, "status": "released", "amount": 1000000 },
{ "id": "9a0b1c2d-3e4f-4a5b-8c6d-7e8f9a0b1c2d", "sequence": 2, "status": "cancelled", "amount": 500000 }
]
}
}Every other deal field is there too; the milestones are shortened here. total_amount stays the sum of every milestone, cancelled ones included.
Errors
| Status | Code | Cause |
|---|---|---|
400 | INVALID_BODY | The body is not empty |
404 | NOT_FOUND | No such deal or milestone in your workspace |
409 | INVALID_STATE | The milestone is already paid or already cancelled. After a payment, open a dispute instead |
409 | PAYMENT_IN_PROGRESS | A checkout payment on this milestone can still land. Wait until it is confirmed or expires |
| Plus the errors every Platform route can answer |
Ship a milestone
Reports the shipment for the seller: paid to shipped. Starts the automatic release clock (72 hours with a tracking number, 36 without, by default). The answer is the milestone.
- Auth: A workspace API key (
Authorization: Bearer rpe_...) sent from your server, or a dashboard session. Personal API keys and app sessions get403 KEY_NOT_ALLOWED. - Scope:
orders, andwriteon the key. - Rate limit: 60 a minute per key, plus the shared 300 a minute of the member who made the key.
- Idempotency: Required with a workspace key: send
Idempotency-Key(UUID v4), or the call answers400 IDEMPOTENCY_KEY_REQUIRED. A retry with the same key and body returns the first answer withIdempotent-Replay: true.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
id | UUID | Yes | The deal id |
mid | UUID | Yes | The milestone id |
Body (JSON)
| Name | Type | Required | Description |
|---|---|---|---|
courier | string | Yes | 1 to 60 characters |
tracking_number | string | null | No | 1 to 80 characters |
Example request
curl -X POST "https://api.rekberpay.com/v1/enterprise/platform/deals/3e4f5a6b-7c8d-4e9f-a0b1-c2d3e4f5a6b7/milestones/4f5a6b7c-8d9e-4fa0-b1c2-d3e4f5a6b7c8/ship" \
-H "Authorization: Bearer $REKBERPAY_WORKSPACE_KEY" \
-H "Idempotency-Key: 8293a4b5-c6d7-42e3-94f5-60718293a4b5" \
-H "Content-Type: application/json" \
-d '{
"courier": "JNE",
"tracking_number": "JNE0012345678"
}'const res = await fetch('https://api.rekberpay.com/v1/enterprise/platform/deals/3e4f5a6b-7c8d-4e9f-a0b1-c2d3e4f5a6b7/milestones/4f5a6b7c-8d9e-4fa0-b1c2-d3e4f5a6b7c8/ship', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.REKBERPAY_WORKSPACE_KEY}`,
'Content-Type': 'application/json',
'Idempotency-Key': '8293a4b5-c6d7-42e3-94f5-60718293a4b5',
},
body: JSON.stringify({
courier: "JNE",
tracking_number: "JNE0012345678",
}),
});
const data = await res.json();import os
import requests
resp = requests.post(
"https://api.rekberpay.com/v1/enterprise/platform/deals/3e4f5a6b-7c8d-4e9f-a0b1-c2d3e4f5a6b7/milestones/4f5a6b7c-8d9e-4fa0-b1c2-d3e4f5a6b7c8/ship",
headers={
"Authorization": f"Bearer {os.environ['REKBERPAY_WORKSPACE_KEY']}",
"Idempotency-Key": "8293a4b5-c6d7-42e3-94f5-60718293a4b5",
},
json={
"courier": "JNE",
"tracking_number": "JNE0012345678",
},
timeout=30,
)
print(resp.status_code, resp.json())<?php
$ch = curl_init('https://api.rekberpay.com/v1/enterprise/platform/deals/3e4f5a6b-7c8d-4e9f-a0b1-c2d3e4f5a6b7/milestones/4f5a6b7c-8d9e-4fa0-b1c2-d3e4f5a6b7c8/ship');
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('REKBERPAY_WORKSPACE_KEY'),
'Content-Type: application/json',
'Idempotency-Key: 8293a4b5-c6d7-42e3-94f5-60718293a4b5',
],
CURLOPT_POSTFIELDS => json_encode([
'courier' => 'JNE',
'tracking_number' => 'JNE0012345678',
]),
]);
$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": "4f5a6b7c-8d9e-4fa0-b1c2-d3e4f5a6b7c8",
"sequence": 1,
"title": "Deposit",
"status": "shipped",
"amount": 1000000,
"buyer_fee": 10000,
"seller_fee": 10000,
"funding_amount": 1010000,
"payout_amount": 990000,
"refund_amount": null,
"paid_at": "2026-10-21T08:12:40.000Z",
"courier": "JNE",
"tracking_number": "JNE0012345678",
"shipped_at": "2026-10-21T09:00:00.000Z",
"received_at": null,
"received_by": null,
"auto_release_at": "2026-10-24T09:00:00.000Z",
"release_at": null,
"released_at": null,
"extend_count": 0,
"disputed_at": null
}
}You get milestone.shipped.
Errors
| Status | Code | Cause |
|---|---|---|
400 | INVALID_BODY | courier or tracking_number is missing or too long |
404 | NOT_FOUND | No such deal or milestone in your workspace |
409 | INVALID_STATE | Only a paid milestone can be shipped |
| Plus the errors every Platform route can answer |
Confirm receipt
Confirms receipt for the buyer: shipped to release_pending. The release waits confirm_delay_hours (6 by default). The buyer is told by email and in the app and can stop it with Bukan saya, ajukan sengketa. See Shipping, confirm and auto release.
- Auth: A workspace API key (
Authorization: Bearer rpe_...) sent from your server, or a dashboard session. Personal API keys and app sessions get403 KEY_NOT_ALLOWED. - Scope:
orders:confirm, andwriteon the key. Off by default; RekberPay must allow it in your terms. - Rate limit: 30 a minute per key, plus the shared 300 a minute of the member who made the key.
- Idempotency: Required with a workspace key: send
Idempotency-Key(UUID v4), or the call answers400 IDEMPOTENCY_KEY_REQUIRED. A retry with the same key and body returns the first answer withIdempotent-Replay: true.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
id | UUID | Yes | The deal id |
mid | UUID | Yes | The milestone id |
Send an empty JSON object, or no body. In the sandbox the answer adds release_notice_url, the page the buyer would get.
Example request
curl -X POST "https://api.rekberpay.com/v1/enterprise/platform/deals/3e4f5a6b-7c8d-4e9f-a0b1-c2d3e4f5a6b7/milestones/4f5a6b7c-8d9e-4fa0-b1c2-d3e4f5a6b7c8/confirm" \
-H "Authorization: Bearer $REKBERPAY_WORKSPACE_KEY" \
-H "Idempotency-Key: 93a4b5c6-d7e8-43f4-a506-718293a4b5c6" \
-H "Content-Type: application/json" \
-d '{}'const res = await fetch('https://api.rekberpay.com/v1/enterprise/platform/deals/3e4f5a6b-7c8d-4e9f-a0b1-c2d3e4f5a6b7/milestones/4f5a6b7c-8d9e-4fa0-b1c2-d3e4f5a6b7c8/confirm', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.REKBERPAY_WORKSPACE_KEY}`,
'Content-Type': 'application/json',
'Idempotency-Key': '93a4b5c6-d7e8-43f4-a506-718293a4b5c6',
},
body: JSON.stringify({}),
});
const data = await res.json();import os
import requests
resp = requests.post(
"https://api.rekberpay.com/v1/enterprise/platform/deals/3e4f5a6b-7c8d-4e9f-a0b1-c2d3e4f5a6b7/milestones/4f5a6b7c-8d9e-4fa0-b1c2-d3e4f5a6b7c8/confirm",
headers={
"Authorization": f"Bearer {os.environ['REKBERPAY_WORKSPACE_KEY']}",
"Idempotency-Key": "93a4b5c6-d7e8-43f4-a506-718293a4b5c6",
},
json={},
timeout=30,
)
print(resp.status_code, resp.json())<?php
$ch = curl_init('https://api.rekberpay.com/v1/enterprise/platform/deals/3e4f5a6b-7c8d-4e9f-a0b1-c2d3e4f5a6b7/milestones/4f5a6b7c-8d9e-4fa0-b1c2-d3e4f5a6b7c8/confirm');
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('REKBERPAY_WORKSPACE_KEY'),
'Content-Type: application/json',
'Idempotency-Key: 93a4b5c6-d7e8-43f4-a506-718293a4b5c6',
],
CURLOPT_POSTFIELDS => json_encode(new stdClass()),
]);
$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": "4f5a6b7c-8d9e-4fa0-b1c2-d3e4f5a6b7c8",
"sequence": 1,
"title": "Deposit",
"status": "release_pending",
"amount": 1000000,
"buyer_fee": 10000,
"seller_fee": 10000,
"funding_amount": 1010000,
"payout_amount": 990000,
"refund_amount": null,
"paid_at": "2026-10-21T08:12:40.000Z",
"courier": "JNE",
"tracking_number": "JNE0012345678",
"shipped_at": "2026-10-21T09:00:00.000Z",
"received_at": "2026-10-22T04:00:00.000Z",
"received_by": "partner",
"auto_release_at": null,
"release_at": "2026-10-22T10:00:00.000Z",
"released_at": null,
"extend_count": 0,
"disputed_at": null,
"release_notice_url": "https://rekberpay.com/release/rpt.eyJwIjoicmVsZWFzZV9ub3RpY2UiLCJ4IjoxfQ.Zr8nP2kW5vX1cM7qT4yB9sL3hF6dJ0aG2eU5iO8wN3m"
}
}You get milestone.release_pending now and milestone.released at release_at, unless the buyer disputes first.
Errors
| Status | Code | Cause |
|---|---|---|
400 | INVALID_BODY | The body is not empty |
404 | NOT_FOUND | No such deal or milestone in your workspace |
409 | INVALID_STATE | Only a shipped milestone can be confirmed |
409 | USER_NOT_AVAILABLE | The buyer is no longer linked to you |
| Plus the errors every Platform route can answer |
Extend the auto release
The buyer's one extension of the automatic release (48 hours by default), asked through you. The new clock counts from auto_release_at or from now, whichever is later.
- Auth: A workspace API key (
Authorization: Bearer rpe_...) sent from your server, or a dashboard session. Personal API keys and app sessions get403 KEY_NOT_ALLOWED. - Scope:
orders, andwriteon the key. - Rate limit: 60 a minute per key, plus the shared 300 a minute of the member who made the key.
- Idempotency: Required with a workspace key: send
Idempotency-Key(UUID v4), or the call answers400 IDEMPOTENCY_KEY_REQUIRED. A retry with the same key and body returns the first answer withIdempotent-Replay: true.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
id | UUID | Yes | The deal id |
mid | UUID | Yes | The milestone id |
Send an empty JSON object, or no body.
Example request
curl -X POST "https://api.rekberpay.com/v1/enterprise/platform/deals/3e4f5a6b-7c8d-4e9f-a0b1-c2d3e4f5a6b7/milestones/4f5a6b7c-8d9e-4fa0-b1c2-d3e4f5a6b7c8/extend" \
-H "Authorization: Bearer $REKBERPAY_WORKSPACE_KEY" \
-H "Idempotency-Key: a4b5c6d7-e8f9-4405-b617-8293a4b5c6d7" \
-H "Content-Type: application/json" \
-d '{}'const res = await fetch('https://api.rekberpay.com/v1/enterprise/platform/deals/3e4f5a6b-7c8d-4e9f-a0b1-c2d3e4f5a6b7/milestones/4f5a6b7c-8d9e-4fa0-b1c2-d3e4f5a6b7c8/extend', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.REKBERPAY_WORKSPACE_KEY}`,
'Content-Type': 'application/json',
'Idempotency-Key': 'a4b5c6d7-e8f9-4405-b617-8293a4b5c6d7',
},
body: JSON.stringify({}),
});
const data = await res.json();import os
import requests
resp = requests.post(
"https://api.rekberpay.com/v1/enterprise/platform/deals/3e4f5a6b-7c8d-4e9f-a0b1-c2d3e4f5a6b7/milestones/4f5a6b7c-8d9e-4fa0-b1c2-d3e4f5a6b7c8/extend",
headers={
"Authorization": f"Bearer {os.environ['REKBERPAY_WORKSPACE_KEY']}",
"Idempotency-Key": "a4b5c6d7-e8f9-4405-b617-8293a4b5c6d7",
},
json={},
timeout=30,
)
print(resp.status_code, resp.json())<?php
$ch = curl_init('https://api.rekberpay.com/v1/enterprise/platform/deals/3e4f5a6b-7c8d-4e9f-a0b1-c2d3e4f5a6b7/milestones/4f5a6b7c-8d9e-4fa0-b1c2-d3e4f5a6b7c8/extend');
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('REKBERPAY_WORKSPACE_KEY'),
'Content-Type: application/json',
'Idempotency-Key: a4b5c6d7-e8f9-4405-b617-8293a4b5c6d7',
],
CURLOPT_POSTFIELDS => json_encode(new stdClass()),
]);
$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": "4f5a6b7c-8d9e-4fa0-b1c2-d3e4f5a6b7c8",
"sequence": 1,
"title": "Deposit",
"status": "shipped",
"amount": 1000000,
"buyer_fee": 10000,
"seller_fee": 10000,
"funding_amount": 1010000,
"payout_amount": 990000,
"refund_amount": null,
"paid_at": "2026-10-21T08:12:40.000Z",
"courier": "JNE",
"tracking_number": "JNE0012345678",
"shipped_at": "2026-10-21T09:00:00.000Z",
"received_at": null,
"received_by": null,
"auto_release_at": "2026-10-26T09:00:00.000Z",
"release_at": null,
"released_at": null,
"extend_count": 1,
"disputed_at": null
}
}You get milestone.extended.
Errors
| Status | Code | Cause |
|---|---|---|
400 | INVALID_BODY | The body is not empty |
404 | NOT_FOUND | No such deal or milestone in your workspace |
409 | NOT_EXTENDABLE | Only a shipped milestone can be extended |
409 | NO_ACTIVE_TIMER | The milestone has no running automatic release |
409 | EXTEND_LIMIT_REACHED | It was already extended the most times allowed |
409 | EXTEND_DISABLED | RekberPay has switched extensions off |
| Plus the errors every Platform route can answer |
Open a dispute
Opens a dispute for the buyer or the seller on a paid, shipped or release_pending milestone. Every timer stops and a pending confirm is cancelled. RekberPay's mediation team decides. See Disputes on marketplace orders.
- Auth: A workspace API key (
Authorization: Bearer rpe_...) sent from your server, or a dashboard session. Personal API keys and app sessions get403 KEY_NOT_ALLOWED. - Scope:
orders, andwriteon the key. - Rate limit: 60 a minute per key, plus the shared 300 a minute of the member who made the key.
- Idempotency: Required with a workspace key: send
Idempotency-Key(UUID v4), or the call answers400 IDEMPOTENCY_KEY_REQUIRED. A retry with the same key and body returns the first answer withIdempotent-Replay: true.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
id | UUID | Yes | The deal id |
mid | UUID | Yes | The milestone id |
Body (JSON)
| Name | Type | Required | Description |
|---|---|---|---|
reason | string | Yes | 10 to 700 characters, without < or > |
raised_by | string | Yes | buyer or seller |
Example request
curl -X POST "https://api.rekberpay.com/v1/enterprise/platform/deals/3e4f5a6b-7c8d-4e9f-a0b1-c2d3e4f5a6b7/milestones/4f5a6b7c-8d9e-4fa0-b1c2-d3e4f5a6b7c8/dispute" \
-H "Authorization: Bearer $REKBERPAY_WORKSPACE_KEY" \
-H "Idempotency-Key: b5c6d7e8-f90a-4516-8728-93a4b5c6d7e8" \
-H "Content-Type: application/json" \
-d '{
"reason": "The desk arrived with a cracked top. Photos are in the chat.",
"raised_by": "buyer"
}'const res = await fetch('https://api.rekberpay.com/v1/enterprise/platform/deals/3e4f5a6b-7c8d-4e9f-a0b1-c2d3e4f5a6b7/milestones/4f5a6b7c-8d9e-4fa0-b1c2-d3e4f5a6b7c8/dispute', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.REKBERPAY_WORKSPACE_KEY}`,
'Content-Type': 'application/json',
'Idempotency-Key': 'b5c6d7e8-f90a-4516-8728-93a4b5c6d7e8',
},
body: JSON.stringify({
reason: "The desk arrived with a cracked top. Photos are in the chat.",
raised_by: "buyer",
}),
});
const data = await res.json();import os
import requests
resp = requests.post(
"https://api.rekberpay.com/v1/enterprise/platform/deals/3e4f5a6b-7c8d-4e9f-a0b1-c2d3e4f5a6b7/milestones/4f5a6b7c-8d9e-4fa0-b1c2-d3e4f5a6b7c8/dispute",
headers={
"Authorization": f"Bearer {os.environ['REKBERPAY_WORKSPACE_KEY']}",
"Idempotency-Key": "b5c6d7e8-f90a-4516-8728-93a4b5c6d7e8",
},
json={
"reason": "The desk arrived with a cracked top. Photos are in the chat.",
"raised_by": "buyer",
},
timeout=30,
)
print(resp.status_code, resp.json())<?php
$ch = curl_init('https://api.rekberpay.com/v1/enterprise/platform/deals/3e4f5a6b-7c8d-4e9f-a0b1-c2d3e4f5a6b7/milestones/4f5a6b7c-8d9e-4fa0-b1c2-d3e4f5a6b7c8/dispute');
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('REKBERPAY_WORKSPACE_KEY'),
'Content-Type: application/json',
'Idempotency-Key: b5c6d7e8-f90a-4516-8728-93a4b5c6d7e8',
],
CURLOPT_POSTFIELDS => json_encode([
'reason' => 'The desk arrived with a cracked top. Photos are in the chat.',
'raised_by' => 'buyer',
]),
]);
$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": "4f5a6b7c-8d9e-4fa0-b1c2-d3e4f5a6b7c8",
"sequence": 1,
"title": "Deposit",
"status": "disputed",
"amount": 1000000,
"buyer_fee": 10000,
"seller_fee": 10000,
"funding_amount": 1010000,
"payout_amount": 990000,
"refund_amount": null,
"paid_at": "2026-10-21T08:12:40.000Z",
"courier": "JNE",
"tracking_number": "JNE0012345678",
"shipped_at": "2026-10-21T09:00:00.000Z",
"received_at": null,
"received_by": null,
"auto_release_at": null,
"release_at": null,
"released_at": null,
"extend_count": 0,
"disputed_at": "2026-10-22T02:00:00.000Z"
}
}You get dispute.opened, and later dispute.resolved with outcome:
outcome | Milestone | Money |
|---|---|---|
release | released | The seller's partner balance gets payout_amount |
refund | refunded | The buyer's partner balance gets amount plus buyer_fee. The payment method fee is not refunded |
split | released | The buyer gets refund_amount plus the same share of buyer_fee, the seller the rest minus the same share of seller_fee, rounded down |
resume | shipped | Nothing moves. The automatic release restarts with the time that was left. A dispute opened during your pending confirm gets at least the full shipping clock again |
Errors
| Status | Code | Cause |
|---|---|---|
400 | INVALID_BODY | reason or raised_by is missing or not valid |
404 | NOT_FOUND | No such deal or milestone in your workspace |
409 | INVALID_STATE | The milestone cannot be disputed in its current status |
| Plus the errors every Platform route can answer |
Add dispute evidence
Adds a statement and files to an open dispute, as multipart/form-data. Only RekberPay's mediation team sees them.
- Auth: A workspace API key (
Authorization: Bearer rpe_...) sent from your server, or a dashboard session. Personal API keys and app sessions get403 KEY_NOT_ALLOWED. - Scope:
orders, andwriteon the key. - Rate limit: 60 a minute per key, plus the shared 300 a minute of the member who made the key.
- Idempotency: Required. The key is bound to the path, not to the form: a reused key replays the first answer and the new form is not processed. Use a new key for every upload.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
id | UUID | Yes | The deal id |
mid | UUID | Yes | The milestone id |
Body (multipart/form-data)
| Name | Type | Required | Description |
|---|---|---|---|
note | text | Yes | One part, 3 to 700 characters, without < or > |
files | file | Yes | 1 to 5 parts with this name. JPG, PNG, WebP or PDF, up to 10 MB each. At most 50 files per dispute |
Example request
curl -X POST "https://api.rekberpay.com/v1/enterprise/platform/deals/3e4f5a6b-7c8d-4e9f-a0b1-c2d3e4f5a6b7/milestones/4f5a6b7c-8d9e-4fa0-b1c2-d3e4f5a6b7c8/dispute/evidence" \
-H "Authorization: Bearer $REKBERPAY_WORKSPACE_KEY" \
-H "Idempotency-Key: c6d7e8f9-0a1b-4627-9839-a4b5c6d7e8f9" \
-F "note=Photos of the cracked top and the courier receipt." \
-F "[email protected]" \
-F "[email protected]"import { readFile } from 'node:fs/promises';
const form = new FormData();
form.append("note", "Photos of the cracked top and the courier receipt.");
form.append("files", new Blob([await readFile("desk-top.jpg")], { type: "image/jpeg" }), "desk-top.jpg");
form.append("files", new Blob([await readFile("courier-receipt.pdf")], { type: "application/pdf" }), "courier-receipt.pdf");
const res = await fetch('https://api.rekberpay.com/v1/enterprise/platform/deals/3e4f5a6b-7c8d-4e9f-a0b1-c2d3e4f5a6b7/milestones/4f5a6b7c-8d9e-4fa0-b1c2-d3e4f5a6b7c8/dispute/evidence', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.REKBERPAY_WORKSPACE_KEY}`,
'Idempotency-Key': 'c6d7e8f9-0a1b-4627-9839-a4b5c6d7e8f9',
},
body: form,
});
const data = await res.json();import os
import requests
resp = requests.post(
"https://api.rekberpay.com/v1/enterprise/platform/deals/3e4f5a6b-7c8d-4e9f-a0b1-c2d3e4f5a6b7/milestones/4f5a6b7c-8d9e-4fa0-b1c2-d3e4f5a6b7c8/dispute/evidence",
headers={
"Authorization": f"Bearer {os.environ['REKBERPAY_WORKSPACE_KEY']}",
"Idempotency-Key": "c6d7e8f9-0a1b-4627-9839-a4b5c6d7e8f9",
},
data={
"note": "Photos of the cracked top and the courier receipt.",
},
files=[
("files", ("desk-top.jpg", open("desk-top.jpg", "rb"), "image/jpeg")),
("files", ("courier-receipt.pdf", open("courier-receipt.pdf", "rb"), "application/pdf")),
],
timeout=30,
)
print(resp.status_code, resp.json())<?php
$ch = curl_init('https://api.rekberpay.com/v1/enterprise/platform/deals/3e4f5a6b-7c8d-4e9f-a0b1-c2d3e4f5a6b7/milestones/4f5a6b7c-8d9e-4fa0-b1c2-d3e4f5a6b7c8/dispute/evidence');
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('REKBERPAY_WORKSPACE_KEY'),
'Idempotency-Key: c6d7e8f9-0a1b-4627-9839-a4b5c6d7e8f9',
],
CURLOPT_POSTFIELDS => [
'note' => 'Photos of the cracked top and the courier receipt.',
'files' => new CURLFile('desk-top.jpg', 'image/jpeg'),
],
]);
$response = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
curl_close($ch);Example response
201 Created
{
"success": true,
"data": {
"milestone_id": "4f5a6b7c-8d9e-4fa0-b1c2-d3e4f5a6b7c8",
"status": "disputed",
"files_added": 2
}
}The PHP example sends one file: a PHP array cannot repeat the files name. Send one call per file, each with its own Idempotency-Key.
Errors
| Status | Code | Cause |
|---|---|---|
400 | INVALID_BODY | Not multipart, no file, a part with another name, or the note is not valid |
404 | NOT_FOUND | No such deal or milestone in your workspace |
409 | NOT_DISPUTED | The dispute is not open |
409 | EVIDENCE_LIMIT_REACHED | The dispute already holds 50 files |
413 | FILE_TOO_LARGE | A file is over 10 MB |
400 | TYPE_NOT_ALLOWED | A file is not JPG, PNG, WebP or PDF |
400 | TYPE_MISMATCH | The content of a file does not match its type or extension |
400 | IMAGE_DECODE_FAILED | An image is damaged or not a real image |
400 | PDF_ENCRYPTED | A PDF is locked, damaged or holds content that cannot be removed. Also PDF_PASSWORD_PROTECTED, PDF_ACTIVE_CONTENT, PDF_EMBEDDED_FILE, PDF_DAMAGED, PDF_UNSUPPORTED_ENCODING, PDF_TOO_MANY_PAGES. Send a photo or a re-saved PDF |
429 | QUOTA_EXCEEDED | The daily upload limit (200 files) of the member who made the key is reached |
500 | STORAGE_FAILED | A file could not be stored. Nothing was added. Retry with a new Idempotency-Key |
503 | PDF_CHECK_UNAVAILABLE | RekberPay cannot check PDFs right now. Retry later or send photos |
| Plus the errors every Platform route can answer |
Advance a milestone timer (sandbox)
Releases a sandbox milestone now, as if its time ran out. A shipped milestone is released like the automatic release (received_by: "auto", event milestone.auto_released). A release_pending milestone, after your confirm, is released like the end of the confirm wait (received_by: "partner", event milestone.released). The seller's test partner balance gets payout_amount. The answer is the milestone.
- Auth: A workspace API key (
Authorization: Bearer rpe_...) sent from your server, or a dashboard session. Personal API keys and app sessions get403 KEY_NOT_ALLOWED. - Scope:
orders, andwriteon the key. - Rate limit: 30 a minute per key, plus the shared 300 a minute of the member who made the key.
- Idempotency: Required with a workspace key: send
Idempotency-Key(UUID v4), or the call answers400 IDEMPOTENCY_KEY_REQUIRED. A retry with the same key and body returns the first answer withIdempotent-Replay: true. - Sandbox only. A Live request gets
404 NOT_FOUND, whatever the scope.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
id | UUID | Yes | The deal id |
mid | UUID | Yes | The milestone id |
Send an empty JSON object, or no body.
Example request
curl -X POST "https://api.rekberpay.com/v1/enterprise/platform/deals/3e4f5a6b-7c8d-4e9f-a0b1-c2d3e4f5a6b7/milestones/4f5a6b7c-8d9e-4fa0-b1c2-d3e4f5a6b7c8/simulate/advance-time" \
-H "Authorization: Bearer $REKBERPAY_WORKSPACE_KEY" \
-H "Idempotency-Key: d7e8f90a-1b2c-4738-a94a-b5c6d7e8f90a" \
-H "Content-Type: application/json" \
-d '{}'const res = await fetch('https://api.rekberpay.com/v1/enterprise/platform/deals/3e4f5a6b-7c8d-4e9f-a0b1-c2d3e4f5a6b7/milestones/4f5a6b7c-8d9e-4fa0-b1c2-d3e4f5a6b7c8/simulate/advance-time', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.REKBERPAY_WORKSPACE_KEY}`,
'Content-Type': 'application/json',
'Idempotency-Key': 'd7e8f90a-1b2c-4738-a94a-b5c6d7e8f90a',
},
body: JSON.stringify({}),
});
const data = await res.json();import os
import requests
resp = requests.post(
"https://api.rekberpay.com/v1/enterprise/platform/deals/3e4f5a6b-7c8d-4e9f-a0b1-c2d3e4f5a6b7/milestones/4f5a6b7c-8d9e-4fa0-b1c2-d3e4f5a6b7c8/simulate/advance-time",
headers={
"Authorization": f"Bearer {os.environ['REKBERPAY_WORKSPACE_KEY']}",
"Idempotency-Key": "d7e8f90a-1b2c-4738-a94a-b5c6d7e8f90a",
},
json={},
timeout=30,
)
print(resp.status_code, resp.json())<?php
$ch = curl_init('https://api.rekberpay.com/v1/enterprise/platform/deals/3e4f5a6b-7c8d-4e9f-a0b1-c2d3e4f5a6b7/milestones/4f5a6b7c-8d9e-4fa0-b1c2-d3e4f5a6b7c8/simulate/advance-time');
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('REKBERPAY_WORKSPACE_KEY'),
'Content-Type: application/json',
'Idempotency-Key: d7e8f90a-1b2c-4738-a94a-b5c6d7e8f90a',
],
CURLOPT_POSTFIELDS => json_encode(new stdClass()),
]);
$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": "4f5a6b7c-8d9e-4fa0-b1c2-d3e4f5a6b7c8",
"sequence": 1,
"title": "Deposit",
"status": "released",
"amount": 1000000,
"buyer_fee": 10000,
"seller_fee": 10000,
"funding_amount": 1010000,
"payout_amount": 990000,
"refund_amount": null,
"paid_at": "2026-10-21T08:12:40.000Z",
"courier": "JNE",
"tracking_number": "JNE0012345678",
"shipped_at": "2026-10-21T09:00:00.000Z",
"received_at": "2026-10-21T09:30:00.000Z",
"received_by": "auto",
"auto_release_at": null,
"release_at": null,
"released_at": "2026-10-21T09:30:00.000Z",
"extend_count": 0,
"disputed_at": null
}
}Errors
| Status | Code | Cause |
|---|---|---|
400 | INVALID_BODY | The body is not empty |
404 | NOT_FOUND | A Live request, or no such deal or milestone in your workspace |
409 | INVALID_STATE | The milestone is not shipped or release_pending, it is disputed, or it was already settled |
| Plus the errors every Platform route can answer |
Read a balance
The user's partner balance: money from your orders only, in whole rupiah. main_balance is added only when the person allowed you to manage their whole balance.
- Auth: A workspace API key (
Authorization: Bearer rpe_...) sent from your server, or a dashboard session. Personal API keys and app sessions get403 KEY_NOT_ALLOWED. - Scope:
balance:read. The key must be made by afinancemember or higher. - Rate limit: 120 a minute per key, plus the shared 300 a minute of the member who made the key.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
ref | string | Yes | Your partner_user_ref |
Example request
curl "https://api.rekberpay.com/v1/enterprise/platform/users/seller-2001/balance" \
-H "Authorization: Bearer $REKBERPAY_WORKSPACE_KEY"const res = await fetch('https://api.rekberpay.com/v1/enterprise/platform/users/seller-2001/balance', {
headers: {
Authorization: `Bearer ${process.env.REKBERPAY_WORKSPACE_KEY}`,
},
});
const data = await res.json();import os
import requests
resp = requests.get(
"https://api.rekberpay.com/v1/enterprise/platform/users/seller-2001/balance",
headers={
"Authorization": f"Bearer {os.environ['REKBERPAY_WORKSPACE_KEY']}",
},
timeout=30,
)
print(resp.status_code, resp.json())<?php
$ch = curl_init('https://api.rekberpay.com/v1/enterprise/platform/users/seller-2001/balance');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('REKBERPAY_WORKSPACE_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": {
"partner_balance": 990000
}
}Errors
| Status | Code | Cause |
|---|---|---|
404 | NOT_FOUND | No active link with this ref, or the ref is not yours |
| Plus the errors every Platform route can answer |
List bank accounts
The payout bank accounts you added for this user. Numbers are masked.
- Auth: A workspace API key (
Authorization: Bearer rpe_...) sent from your server, or a dashboard session. Personal API keys and app sessions get403 KEY_NOT_ALLOWED. - Scope:
withdraw. The key must be made by afinancemember or higher. - Rate limit: 120 a minute per key, plus the shared 300 a minute of the member who made the key.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
ref | string | Yes | Your partner_user_ref |
Example request
curl "https://api.rekberpay.com/v1/enterprise/platform/users/seller-2001/bank-accounts" \
-H "Authorization: Bearer $REKBERPAY_WORKSPACE_KEY"const res = await fetch('https://api.rekberpay.com/v1/enterprise/platform/users/seller-2001/bank-accounts', {
headers: {
Authorization: `Bearer ${process.env.REKBERPAY_WORKSPACE_KEY}`,
},
});
const data = await res.json();import os
import requests
resp = requests.get(
"https://api.rekberpay.com/v1/enterprise/platform/users/seller-2001/bank-accounts",
headers={
"Authorization": f"Bearer {os.environ['REKBERPAY_WORKSPACE_KEY']}",
},
timeout=30,
)
print(resp.status_code, resp.json())<?php
$ch = curl_init('https://api.rekberpay.com/v1/enterprise/platform/users/seller-2001/bank-accounts');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('REKBERPAY_WORKSPACE_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": "5a6b7c8d-9e0f-4a1b-8c2d-3e4f5a6b7c8d",
"bank_code": "BCA",
"account_number_masked": "•••• 7890",
"holder_name": "Budi Santoso",
"usable_after": "2026-10-22T08:00:00.000Z",
"usable": false,
"is_test": true,
"created_at": "2026-10-21T08:00:00.000Z"
}
]
}Errors
| Status | Code | Cause |
|---|---|---|
404 | NOT_FOUND | No active link with this ref, or the ref is not yours |
| Plus the errors every Platform route can answer |
Add a bank account
Adds a payout bank account for the user. The holder name must match the user's name on RekberPay, and the account can receive withdrawals only after its cooling-off (24 hours by default). See Balances and withdrawals.
- Auth: A workspace API key (
Authorization: Bearer rpe_...) sent from your server, or a dashboard session. Personal API keys and app sessions get403 KEY_NOT_ALLOWED. - Scope:
withdraw, andwriteon the key. The key must be made by afinancemember or higher. - Rate limit: 30 a minute per key, plus the shared 300 a minute of the member who made the key.
- Idempotency: Required with a workspace key: send
Idempotency-Key(UUID v4), or the call answers400 IDEMPOTENCY_KEY_REQUIRED. A retry with the same key and body returns the first answer withIdempotent-Replay: true.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
ref | string | Yes | Your partner_user_ref |
Body (JSON)
| Name | Type | Required | Description |
|---|---|---|---|
bank_code | string | Yes | The bank code, 2 to 40 letters, digits or _, for example BCA, with no run of 10 or more digits (that is an account number). Stored in upper case and never editable. It sets the withdrawal fee: use one of the accepted bank codes for BI-FAST |
account_number | string | Yes | 5 to 30 digits, without spaces |
holder_name | string | Yes | 1 to 120 characters. Must match the KTP name once KYC is approved, else the account name |
Example request
curl -X POST "https://api.rekberpay.com/v1/enterprise/platform/users/seller-2001/bank-accounts" \
-H "Authorization: Bearer $REKBERPAY_WORKSPACE_KEY" \
-H "Idempotency-Key: e8f90a1b-2c3d-4849-ba5b-c6d7e8f90a1b" \
-H "Content-Type: application/json" \
-d '{
"bank_code": "BCA",
"account_number": "1234567890",
"holder_name": "Budi Santoso"
}'const res = await fetch('https://api.rekberpay.com/v1/enterprise/platform/users/seller-2001/bank-accounts', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.REKBERPAY_WORKSPACE_KEY}`,
'Content-Type': 'application/json',
'Idempotency-Key': 'e8f90a1b-2c3d-4849-ba5b-c6d7e8f90a1b',
},
body: JSON.stringify({
bank_code: "BCA",
account_number: "1234567890",
holder_name: "Budi Santoso",
}),
});
const data = await res.json();import os
import requests
resp = requests.post(
"https://api.rekberpay.com/v1/enterprise/platform/users/seller-2001/bank-accounts",
headers={
"Authorization": f"Bearer {os.environ['REKBERPAY_WORKSPACE_KEY']}",
"Idempotency-Key": "e8f90a1b-2c3d-4849-ba5b-c6d7e8f90a1b",
},
json={
"bank_code": "BCA",
"account_number": "1234567890",
"holder_name": "Budi Santoso",
},
timeout=30,
)
print(resp.status_code, resp.json())<?php
$ch = curl_init('https://api.rekberpay.com/v1/enterprise/platform/users/seller-2001/bank-accounts');
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('REKBERPAY_WORKSPACE_KEY'),
'Content-Type: application/json',
'Idempotency-Key: e8f90a1b-2c3d-4849-ba5b-c6d7e8f90a1b',
],
CURLOPT_POSTFIELDS => json_encode([
'bank_code' => 'BCA',
'account_number' => '1234567890',
'holder_name' => 'Budi Santoso',
]),
]);
$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": "5a6b7c8d-9e0f-4a1b-8c2d-3e4f5a6b7c8d",
"bank_code": "BCA",
"account_number_masked": "•••• 7890",
"holder_name": "Budi Santoso",
"usable_after": "2026-10-22T08:00:00.000Z",
"usable": false,
"is_test": true,
"created_at": "2026-10-21T08:00:00.000Z"
}
}You get bank_account.added in the event feed. A person who controls their own account also gets an email.
Errors
| Status | Code | Cause |
|---|---|---|
400 | INVALID_BODY | A field is missing or not valid. The answer never repeats the number |
404 | NOT_FOUND | No active link with this ref, or the ref is not yours |
409 | BANK_ACCOUNT_EXISTS | This bank and number are already saved. Carries bank_account_id |
409 | BANK_ACCOUNT_LIMIT | The user already has 10 payout bank accounts with you |
422 | NAME_MISMATCH | The holder name does not match. Nothing is stored |
| Plus the errors every Platform route can answer |
Skip a bank account cooling-off (sandbox)
Makes a sandbox payout bank account usable now, so a withdrawal no longer answers 409 BANK_ACCOUNT_COOLING_OFF. An account that is already usable comes back unchanged. The answer is the bank account.
- Auth: A workspace API key (
Authorization: Bearer rpe_...) sent from your server, or a dashboard session. Personal API keys and app sessions get403 KEY_NOT_ALLOWED. - Scope:
withdraw, andwriteon the key. The key must be made by afinancemember or higher. - Rate limit: 30 a minute per key, plus the shared 300 a minute of the member who made the key.
- Idempotency: Required with a workspace key: send
Idempotency-Key(UUID v4), or the call answers400 IDEMPOTENCY_KEY_REQUIRED. A retry with the same key and body returns the first answer withIdempotent-Replay: true. - Sandbox only. A Live request gets
404 NOT_FOUND, whatever the scope.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
ref | string | Yes | Your partner_user_ref |
id | UUID | Yes | The bank account id |
Send an empty JSON object, or no body.
Example request
curl -X POST "https://api.rekberpay.com/v1/enterprise/platform/users/seller-2001/bank-accounts/5a6b7c8d-9e0f-4a1b-8c2d-3e4f5a6b7c8d/simulate/skip-cooling-off" \
-H "Authorization: Bearer $REKBERPAY_WORKSPACE_KEY" \
-H "Idempotency-Key: f90a1b2c-3d4e-495a-8b6c-d7e8f90a1b2c" \
-H "Content-Type: application/json" \
-d '{}'const res = await fetch('https://api.rekberpay.com/v1/enterprise/platform/users/seller-2001/bank-accounts/5a6b7c8d-9e0f-4a1b-8c2d-3e4f5a6b7c8d/simulate/skip-cooling-off', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.REKBERPAY_WORKSPACE_KEY}`,
'Content-Type': 'application/json',
'Idempotency-Key': 'f90a1b2c-3d4e-495a-8b6c-d7e8f90a1b2c',
},
body: JSON.stringify({}),
});
const data = await res.json();import os
import requests
resp = requests.post(
"https://api.rekberpay.com/v1/enterprise/platform/users/seller-2001/bank-accounts/5a6b7c8d-9e0f-4a1b-8c2d-3e4f5a6b7c8d/simulate/skip-cooling-off",
headers={
"Authorization": f"Bearer {os.environ['REKBERPAY_WORKSPACE_KEY']}",
"Idempotency-Key": "f90a1b2c-3d4e-495a-8b6c-d7e8f90a1b2c",
},
json={},
timeout=30,
)
print(resp.status_code, resp.json())<?php
$ch = curl_init('https://api.rekberpay.com/v1/enterprise/platform/users/seller-2001/bank-accounts/5a6b7c8d-9e0f-4a1b-8c2d-3e4f5a6b7c8d/simulate/skip-cooling-off');
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('REKBERPAY_WORKSPACE_KEY'),
'Content-Type: application/json',
'Idempotency-Key: f90a1b2c-3d4e-495a-8b6c-d7e8f90a1b2c',
],
CURLOPT_POSTFIELDS => json_encode(new stdClass()),
]);
$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": "5a6b7c8d-9e0f-4a1b-8c2d-3e4f5a6b7c8d",
"bank_code": "BCA",
"account_number_masked": "•••• 7890",
"holder_name": "Budi Santoso",
"usable_after": "2026-10-21T08:10:00.000Z",
"usable": true,
"is_test": true,
"created_at": "2026-10-21T08:00:00.000Z"
}
}Errors
| Status | Code | Cause |
|---|---|---|
400 | INVALID_BODY | The body is not empty |
404 | NOT_FOUND | A Live request, or no active link with this ref |
404 | BANK_ACCOUNT_NOT_FOUND | The account is not this user's with you, or it was removed |
| Plus the errors every Platform route can answer |
Request a withdrawal
Pays out part or all of the balance to a saved bank account. The usual RekberPay limits, fee (Withdrawal fees) and fraud screening apply. A large live withdrawal starts pending_review and waits for a second RekberPay approver. A sandbox withdrawal completes at once.
- Auth: A workspace API key (
Authorization: Bearer rpe_...) sent from your server, or a dashboard session. Personal API keys and app sessions get403 KEY_NOT_ALLOWED. - Scope:
withdraw, andwriteon the key. The key must be made by afinancemember or higher. - Rate limit: 30 a minute per key, plus the shared 300 a minute of the member who made the key.
- Idempotency: Required with a workspace key: send
Idempotency-Key(UUID v4), or the call answers400 IDEMPOTENCY_KEY_REQUIRED. A retry with the same key and body returns the first answer withIdempotent-Replay: true.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
ref | string | Yes | Your partner_user_ref |
Body (JSON)
| Name | Type | Required | Description |
|---|---|---|---|
amount | integer | Yes | Whole rupiah, the fee included |
bank_account_id | UUID | Yes | A saved bank account of this user, past its cooling-off |
from | string | No | partner (default) or main. main needs full_balance consent and never works in the sandbox |
Example request
curl -X POST "https://api.rekberpay.com/v1/enterprise/platform/users/seller-2001/withdrawals" \
-H "Authorization: Bearer $REKBERPAY_WORKSPACE_KEY" \
-H "Content-Type: application/json" \
-d '{
"amount": 990000,
"bank_account_id": "5a6b7c8d-9e0f-4a1b-8c2d-3e4f5a6b7c8d"
}'const res = await fetch('https://api.rekberpay.com/v1/enterprise/platform/users/seller-2001/withdrawals', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.REKBERPAY_WORKSPACE_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
amount: 990000,
bank_account_id: "5a6b7c8d-9e0f-4a1b-8c2d-3e4f5a6b7c8d",
}),
});
const data = await res.json();import os
import requests
resp = requests.post(
"https://api.rekberpay.com/v1/enterprise/platform/users/seller-2001/withdrawals",
headers={
"Authorization": f"Bearer {os.environ['REKBERPAY_WORKSPACE_KEY']}",
},
json={
"amount": 990000,
"bank_account_id": "5a6b7c8d-9e0f-4a1b-8c2d-3e4f5a6b7c8d",
},
timeout=30,
)
print(resp.status_code, resp.json())<?php
$ch = curl_init('https://api.rekberpay.com/v1/enterprise/platform/users/seller-2001/withdrawals');
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('REKBERPAY_WORKSPACE_KEY'),
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => json_encode([
'amount' => 990000,
'bank_account_id' => '5a6b7c8d-9e0f-4a1b-8c2d-3e4f5a6b7c8d',
]),
]);
$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": "6b7c8d9e-0f1a-4b2c-9d3e-4f5a6b7c8d9e",
"partner_user_ref": "seller-2001",
"status": "completed",
"from": "partner",
"amount": 990000,
"fee": 4000,
"fee_rail": "bifast",
"net_amount": 986000,
"bank_account_id": "5a6b7c8d-9e0f-4a1b-8c2d-3e4f5a6b7c8d",
"is_test": true,
"created_at": "2026-10-22T09:00:00.000Z",
"updated_at": "2026-10-22T09:00:00.000Z",
"processed_at": "2026-10-22T09:00:00.000Z"
}
}status is pending, pending_review, processing, completed or rejected. You get withdrawal.requested, then withdrawal.settled or withdrawal.failed (the money is back in the balance).
Errors
| Status | Code | Cause |
|---|---|---|
400 | INVALID_BODY | A field is missing or not valid |
403 | RISK_BLOCKED | RekberPay screening refused it. The user can contact RekberPay support |
404 | NOT_FOUND | No active link with this ref, or main without full_balance consent |
404 | BANK_ACCOUNT_NOT_FOUND | No such bank account for this user |
409 | BANK_ACCOUNT_COOLING_OFF | The bank account is still in cooling-off. Carries usable_after |
409 | ACCOUNT_NOT_ACTIVE | The account is closing |
409 | TRY_AGAIN | Another change to this user was saved at the same moment. Nothing was done. Retry the same request |
422 | INVALID_AMOUNT | The amount is outside the limits. Carries limits (min_idr, max_idr, step_idr). The whole partner balance may always be withdrawn, even under the minimum or off the step, while it is more than the fee |
422 | INSUFFICIENT_BALANCE | The balance is too low |
422 | FEE_EXCEEDS_AMOUNT | The withdrawal fee is not lower than the amount |
422 | NAME_MISMATCH | The bank account holder no longer matches the user's name |
422 | SANDBOX_MAIN_BALANCE | A sandbox withdrawal asked for main |
500 | WITHDRAWAL_FAILED | The withdrawal could not be saved. Nothing was charged. Retry |
| Plus the errors every Platform route can answer |
Withdrawal fees
A withdrawal pays one fee, set by the bank rail RekberPay pays it on. The fee comes out of amount, the bank receives net_amount, and the answer names the rail in fee_rail.
fee_rail | When | Fee (default) |
|---|---|---|
bifast | The bank account's bank_code is one of the accepted bank codes | Rp4.000 |
online_transfer | Any other bank_code (a typo, a bank RekberPay does not list), or a listed bank RekberPay has marked as not on BI-FAST | Rp8.000 |
null | The bank_code names an e-wallet: GOPAY, OVO, DANA, SHOPEEPAY or LINKAJA. It pays RekberPay's e-wallet withdrawal fee | Set by RekberPay |
The user's RekberPay loyalty discount or withdrawal fee voucher can lower the fee; fee is what was charged. RekberPay may change the two rail fees and the banks marked as not on BI-FAST: GET /v1/enterprise/platform-status returns the current ones in terms. A sandbox withdrawal is priced the same way, with test money.
A saved bank account cannot be edited. To move an account to BI-FAST, add it again with an accepted code; the new account waits out the cooling-off (24 hours by default) like any other.
Accepted bank codes
A bank_code gets BI-FAST when it is one of the codes below, or the bank's Bank Indonesia 3-digit code (sandi bank) written exactly, leading zeros included (014 is BCA, 14 is not recognised). Case, _ and a leading BANK do not matter, and common short names work too (CIMB for CIMB_NIAGA, JENIUS for BTPN). Anything else pays Online Transfer.
bank_code | Bank | Bank Indonesia code | Rail |
|---|---|---|---|
BCA | BCA | 014 | BI-FAST |
BRI | BRI | 002 | BI-FAST |
BNI | BNI | 009 | BI-FAST |
MANDIRI | Mandiri | 008 | BI-FAST |
BSI | BSI (Bank Syariah Indonesia) | 451 | BI-FAST |
BTN | BTN | 200 | BI-FAST |
CIMB_NIAGA | CIMB Niaga | 022 | BI-FAST |
PERMATA | Permata | 013 | BI-FAST |
DANAMON | Danamon | 011 | BI-FAST |
ALLO_BANK | Allo Bank | 567 | BI-FAST |
ANZ | ANZ Indonesia | 061 | BI-FAST |
BANK_ACEH_SYARIAH | Bank Aceh Syariah | 116 | BI-FAST |
ALADIN_SYARIAH | Bank Aladin Syariah | 947 | BI-FAST |
AMAR | Bank Amar Indonesia | 531 | BI-FAST |
ARTHA_GRAHA | Bank Artha Graha | 037 | BI-FAST |
BANK_BANTEN | Bank Banten | 137 | BI-FAST |
BANK_BENGKULU | Bank Bengkulu | 133 | BI-FAST |
BJB | Bank BJB (Jawa Barat dan Banten) | 110 | BI-FAST |
BJB_SYARIAH | Bank BJB Syariah | 425 | BI-FAST |
BPD_DIY | Bank BPD DIY | 112 | BI-FAST |
BUMI_ARTA | Bank Bumi Arta | 076 | BI-FAST |
BANK_CAPITAL | Bank Capital Indonesia | 054 | BI-FAST |
COMMONWEALTH | Bank Commonwealth | none | BI-FAST |
BANK_DKI | Bank DKI | 111 | BI-FAST |
GANESHA | Bank Ganesha | 161 | BI-FAST |
BANK_INA | Bank Ina Perdana | 513 | BI-FAST |
BANK_INDEX | Bank Index Selindo | 555 | BI-FAST |
BANK_JAMBI | Bank Jambi | 115 | BI-FAST |
JASA_JAKARTA | Bank Jasa Jakarta | 472 | BI-FAST |
BANK_JATENG | Bank Jateng | 113 | BI-FAST |
BANK_JATIM | Bank Jatim | 114 | BI-FAST |
BANK_KALBAR | Bank Kalbar | 123 | BI-FAST |
BANK_KALSEL | Bank Kalsel | 122 | BI-FAST |
BANK_KALTENG | Bank Kalteng | 125 | BI-FAST |
BANK_KALTIMTARA | Bank Kaltimtara | 124 | BI-FAST |
BANK_LAMPUNG | Bank Lampung | 121 | BI-FAST |
BANK_MALUKU_MALUT | Bank Maluku Malut | 131 | BI-FAST |
MANDIRI_TASPEN | Bank Mandiri Taspen | 564 | BI-FAST |
MASPION | Bank Maspion | 157 | BI-FAST |
MAYAPADA | Bank Mayapada | 097 | BI-FAST |
MAYORA | Bank Mayora | none | BI-FAST |
MEGA_SYARIAH | Bank Mega Syariah | 506 | BI-FAST |
MESTIKA | Bank Mestika Dharma | 151 | BI-FAST |
MUAMALAT | Bank Muamalat | 147 | BI-FAST |
MULTIARTA | Bank Multiarta Sentosa | 548 | BI-FAST |
BANK_NAGARI | Bank Nagari | 118 | BI-FAST |
NEO_COMMERCE | Bank Neo Commerce | 490 | BI-FAST |
NOBU | Bank Nobu | 503 | BI-FAST |
BANK_NTB_SYARIAH | Bank NTB Syariah | 128 | BI-FAST |
BANK_NTT | Bank NTT | 130 | BI-FAST |
BANK_OF_AMERICA | Bank of America | 033 | BI-FAST |
BANK_OF_CHINA | Bank of China | 069 | BI-FAST |
BANK_OKE | Bank Oke Indonesia | 526 | BI-FAST |
BANK_PAPUA | Bank Papua | 132 | BI-FAST |
BANK_RAYA | Bank Raya | 494 | BI-FAST |
RESONA_PERDANIA | Bank Resona Perdania | 047 | BI-FAST |
BANK_RIAU_KEPRI | Bank Riau Kepri Syariah | 119 | BI-FAST |
SAMPOERNA | Bank Sahabat Sampoerna | 523 | BI-FAST |
SINARMAS | Bank Sinarmas | 153 | BI-FAST |
BANK_SULSELBAR | Bank Sulselbar | 126 | BI-FAST |
BANK_SULTENG | Bank Sulteng | 134 | BI-FAST |
BANK_SULTRA | Bank Sultra | 135 | BI-FAST |
BANK_SULUTGO | Bank SulutGo | 127 | BI-FAST |
BANK_SUMSEL_BABEL | Bank Sumsel Babel | 120 | BI-FAST |
BANK_SUMUT | Bank Sumut | 117 | BI-FAST |
VICTORIA | Bank Victoria | 566 | BI-FAST |
WOORI_SAUDARA | Bank Woori Saudara | 212 | BI-FAST |
BCA_SYARIAH | BCA Syariah | 536 | BI-FAST |
BLU | Blu by BCA Digital | 501 | BI-FAST |
BNP_PARIBAS | BNP Paribas Indonesia | 057 | BI-FAST |
BPD_BALI | BPD Bali | 129 | BI-FAST |
BTPN | BTPN (Jenius) | 213 | BI-FAST |
BTPN_SYARIAH | BTPN Syariah | 547 | BI-FAST |
CCB | China Construction Bank Indonesia | 036 | BI-FAST |
CITIBANK | Citibank | 031 | BI-FAST |
CTBC | CTBC Indonesia | 949 | BI-FAST |
DBS | DBS Indonesia | 046 | BI-FAST |
HSBC | HSBC Indonesia | 087 | BI-FAST |
IBK | IBK Indonesia | 945 | BI-FAST |
ICBC | ICBC Indonesia | 164 | BI-FAST |
JTRUST | J Trust Bank Indonesia | 095 | BI-FAST |
JAGO | Jago | 542 | BI-FAST |
JPMORGAN | JPMorgan Chase Bank | 032 | BI-FAST |
KB_BUKOPIN | KB Bukopin | 441 | BI-FAST |
KB_BUKOPIN_SYARIAH | KB Bukopin Syariah | 521 | BI-FAST |
KEB_HANA | KEB Hana Indonesia | 484 | BI-FAST |
MAYBANK | Maybank | 016 | BI-FAST |
MEGA | Mega | 426 | BI-FAST |
MIZUHO | Mizuho Indonesia | 048 | BI-FAST |
MNC | MNC Bank | 485 | BI-FAST |
MUFG | MUFG Bank | 042 | BI-FAST |
OCBC_NISP | OCBC NISP | 028 | BI-FAST |
PANIN | Panin | 019 | BI-FAST |
PANIN_DUBAI_SYARIAH | Panin Dubai Syariah | 517 | BI-FAST |
QNB | QNB Indonesia | 167 | BI-FAST |
SEABANK | Seabank | 535 | BI-FAST |
SHINHAN | Shinhan Indonesia | 152 | BI-FAST |
STANDARD_CHARTERED | Standard Chartered | 050 | BI-FAST |
UOB | UOB Indonesia | 023 | BI-FAST |
Get a withdrawal
One withdrawal you requested for this user.
- Auth: A workspace API key (
Authorization: Bearer rpe_...) sent from your server, or a dashboard session. Personal API keys and app sessions get403 KEY_NOT_ALLOWED. - Scope:
withdraw. The key must be made by afinancemember or higher. - Rate limit: 120 a minute per key, plus the shared 300 a minute of the member who made the key.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
ref | string | Yes | Your partner_user_ref |
id | UUID | Yes | The withdrawal id |
Example request
curl "https://api.rekberpay.com/v1/enterprise/platform/users/seller-2001/withdrawals/6b7c8d9e-0f1a-4b2c-9d3e-4f5a6b7c8d9e" \
-H "Authorization: Bearer $REKBERPAY_WORKSPACE_KEY"const res = await fetch('https://api.rekberpay.com/v1/enterprise/platform/users/seller-2001/withdrawals/6b7c8d9e-0f1a-4b2c-9d3e-4f5a6b7c8d9e', {
headers: {
Authorization: `Bearer ${process.env.REKBERPAY_WORKSPACE_KEY}`,
},
});
const data = await res.json();import os
import requests
resp = requests.get(
"https://api.rekberpay.com/v1/enterprise/platform/users/seller-2001/withdrawals/6b7c8d9e-0f1a-4b2c-9d3e-4f5a6b7c8d9e",
headers={
"Authorization": f"Bearer {os.environ['REKBERPAY_WORKSPACE_KEY']}",
},
timeout=30,
)
print(resp.status_code, resp.json())<?php
$ch = curl_init('https://api.rekberpay.com/v1/enterprise/platform/users/seller-2001/withdrawals/6b7c8d9e-0f1a-4b2c-9d3e-4f5a6b7c8d9e');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('REKBERPAY_WORKSPACE_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": "6b7c8d9e-0f1a-4b2c-9d3e-4f5a6b7c8d9e",
"partner_user_ref": "seller-2001",
"status": "completed",
"from": "partner",
"amount": 990000,
"fee": 4000,
"fee_rail": "bifast",
"net_amount": 986000,
"bank_account_id": "5a6b7c8d-9e0f-4a1b-8c2d-3e4f5a6b7c8d",
"is_test": true,
"created_at": "2026-10-22T09:00:00.000Z",
"updated_at": "2026-10-22T09:00:00.000Z",
"processed_at": "2026-10-22T09:00:00.000Z"
}
}Errors
| Status | Code | Cause |
|---|---|---|
404 | NOT_FOUND | No such withdrawal for this user |
| Plus the errors every Platform route can answer |
Rate a completed deal
Rates the other side of a completed deal for one side: rater: "buyer" rates the seller, rater: "seller" rates the buyer. You name a side, never a user. One rating per side per deal, whoever sends it first: you, or the person on RekberPay. The rating stays private until the other side rates (then both publish together) or for 3 days, whichever comes first. Published ratings count on the person's public RekberPay profile with a "via" badge naming your marketplace. Sandbox ratings never reach a public profile. See Ratings.
- Auth: A workspace API key (
Authorization: Bearer rpe_...) sent from your server, or a dashboard session. Personal API keys and app sessions get403 KEY_NOT_ALLOWED. - Scope:
ratings, andwriteon the key. New keys getratingsby default; a key made before ratings existed does not have it, so make a new key. - Rate limit: 60 a minute per key, plus the shared 300 a minute of the member who made the key.
- Idempotency: Required with a workspace key: send
Idempotency-Key(UUID v4), or the call answers400 IDEMPOTENCY_KEY_REQUIRED. A retry with the same key and body returns the first answer withIdempotent-Replay: true.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
id | UUID | Yes | The deal id |
Body (JSON)
| Name | Type | Required | Description |
|---|---|---|---|
rater | string | Yes | buyer or seller: the side that gives the rating |
stars | integer | Yes | 1 to 5 |
review | string | No | Up to 1000 characters, without <, > or " |
Example request
curl -X POST "https://api.rekberpay.com/v1/enterprise/platform/deals/3e4f5a6b-7c8d-4e9f-a0b1-c2d3e4f5a6b7/ratings" \
-H "Authorization: Bearer $REKBERPAY_WORKSPACE_KEY" \
-H "Idempotency-Key: c6d7e8f9-0a1b-4c2d-8e3f-a4b5c6d7e8f9" \
-H "Content-Type: application/json" \
-d '{
"rater": "buyer",
"stars": 5,
"review": "Fast shipping, exactly as described."
}'const res = await fetch('https://api.rekberpay.com/v1/enterprise/platform/deals/3e4f5a6b-7c8d-4e9f-a0b1-c2d3e4f5a6b7/ratings', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.REKBERPAY_WORKSPACE_KEY}`,
'Content-Type': 'application/json',
'Idempotency-Key': 'c6d7e8f9-0a1b-4c2d-8e3f-a4b5c6d7e8f9',
},
body: JSON.stringify({
rater: 'buyer',
stars: 5,
review: 'Fast shipping, exactly as described.',
}),
});
const data = await res.json();import os
import requests
resp = requests.post(
"https://api.rekberpay.com/v1/enterprise/platform/deals/3e4f5a6b-7c8d-4e9f-a0b1-c2d3e4f5a6b7/ratings",
headers={
"Authorization": f"Bearer {os.environ['REKBERPAY_WORKSPACE_KEY']}",
"Idempotency-Key": "c6d7e8f9-0a1b-4c2d-8e3f-a4b5c6d7e8f9",
},
json={
"rater": "buyer",
"stars": 5,
"review": "Fast shipping, exactly as described.",
},
timeout=30,
)
print(resp.status_code, resp.json())<?php
$ch = curl_init('https://api.rekberpay.com/v1/enterprise/platform/deals/3e4f5a6b-7c8d-4e9f-a0b1-c2d3e4f5a6b7/ratings');
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('REKBERPAY_WORKSPACE_KEY'),
'Content-Type: application/json',
'Idempotency-Key: c6d7e8f9-0a1b-4c2d-8e3f-a4b5c6d7e8f9',
],
CURLOPT_POSTFIELDS => json_encode([
'rater' => 'buyer',
'stars' => 5,
'review' => 'Fast shipping, exactly as described.',
]),
]);
$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": "7c8d9e0f-1a2b-4c3d-8e4f-5a6b7c8d9e0f",
"deal_id": "3e4f5a6b-7c8d-4e9f-a0b1-c2d3e4f5a6b7",
"rater_role": "buyer",
"submitted_by": "partner",
"stars": 5,
"review": "Fast shipping, exactly as described.",
"created_at": "2026-10-25T03:00:00.000Z",
"published_at": null
}
}published_at is set when the other side had already rated, so both ratings published now. You get rating.created (no stars or review), and rating.published for each rating once it publishes.
Errors
| Status | Code | Cause |
|---|---|---|
400 | INVALID_BODY | rater or stars is missing, or the review is too long or holds <, > or " |
404 | NOT_FOUND | No such deal in your workspace, or the rating side's person is no longer linked to you |
409 | NOT_RATEABLE | The deal is not completed: a part still holds money or is disputed, or every paid part was refunded |
409 | ALREADY_RATED | This side already rated this deal, through you or on RekberPay |
422 | USER_NOT_AVAILABLE | The rating side's account is suspended or closed |
422 | SAME_PARTY | The buyer and the seller are the same person |
| Plus the errors every Platform route can answer |
Get a deal's ratings
Who has rated a deal, and each rating once it is published. Before publication you only see submitted: true, never the stars or the review. A rating RekberPay hid stays submitted: true with rating: null.
- Auth: A workspace API key (
Authorization: Bearer rpe_...) sent from your server, or a dashboard session. Personal API keys and app sessions get403 KEY_NOT_ALLOWED. - Scope:
ratings. - Rate limit: 120 a minute per key, plus the shared 300 a minute of the member who made the key.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
id | UUID | Yes | The deal id |
Example request
curl "https://api.rekberpay.com/v1/enterprise/platform/deals/3e4f5a6b-7c8d-4e9f-a0b1-c2d3e4f5a6b7/ratings" \
-H "Authorization: Bearer $REKBERPAY_WORKSPACE_KEY"const res = await fetch('https://api.rekberpay.com/v1/enterprise/platform/deals/3e4f5a6b-7c8d-4e9f-a0b1-c2d3e4f5a6b7/ratings', {
headers: {
Authorization: `Bearer ${process.env.REKBERPAY_WORKSPACE_KEY}`,
},
});
const data = await res.json();import os
import requests
resp = requests.get(
"https://api.rekberpay.com/v1/enterprise/platform/deals/3e4f5a6b-7c8d-4e9f-a0b1-c2d3e4f5a6b7/ratings",
headers={
"Authorization": f"Bearer {os.environ['REKBERPAY_WORKSPACE_KEY']}",
},
timeout=30,
)
print(resp.status_code, resp.json())<?php
$ch = curl_init('https://api.rekberpay.com/v1/enterprise/platform/deals/3e4f5a6b-7c8d-4e9f-a0b1-c2d3e4f5a6b7/ratings');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('REKBERPAY_WORKSPACE_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": {
"deal_id": "3e4f5a6b-7c8d-4e9f-a0b1-c2d3e4f5a6b7",
"rateable": true,
"buyer": { "submitted": true, "rating": null },
"seller": { "submitted": false, "rating": null }
}
}Errors
| Status | Code | Cause |
|---|---|---|
404 | NOT_FOUND | No such deal in your workspace |
| Plus the errors every Platform route can answer |
Get a user's reputation
A linked user's reputation across RekberPay: the same average, count and completed deals as their public profile, with the 10 latest public reviews (each with via_partner, the marketplace it came from, or null for a direct rekber). profile_url is null while the user has no public profile, for example an account you created that the owner has not claimed (see Unclaimed accounts). In a sandbox workspace only your own test ratings and test deals count, and profile_url is always null.
- Auth: A workspace API key (
Authorization: Bearer rpe_...) sent from your server, or a dashboard session. Personal API keys and app sessions get403 KEY_NOT_ALLOWED. - Scope:
users. - Rate limit: 120 a minute per key, plus the shared 300 a minute of the member who made the key.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
ref | string | Yes | Your partner_user_ref of an active link |
Example request
curl "https://api.rekberpay.com/v1/enterprise/platform/users/seller-2001/reputation" \
-H "Authorization: Bearer $REKBERPAY_WORKSPACE_KEY"const res = await fetch('https://api.rekberpay.com/v1/enterprise/platform/users/seller-2001/reputation', {
headers: {
Authorization: `Bearer ${process.env.REKBERPAY_WORKSPACE_KEY}`,
},
});
const data = await res.json();import os
import requests
resp = requests.get(
"https://api.rekberpay.com/v1/enterprise/platform/users/seller-2001/reputation",
headers={
"Authorization": f"Bearer {os.environ['REKBERPAY_WORKSPACE_KEY']}",
},
timeout=30,
)
print(resp.status_code, resp.json())<?php
$ch = curl_init('https://api.rekberpay.com/v1/enterprise/platform/users/seller-2001/reputation');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('REKBERPAY_WORKSPACE_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": {
"partner_user_ref": "seller-2001",
"average_rating": 4.5,
"rating_count": 2,
"completed_deals": 3,
"is_verified": true,
"profile_url": "https://rekberpay.com/u/toko_sari",
"recent_ratings": [
{
"stars": 5,
"review": "Fast shipping, exactly as described.",
"created_at": "2026-10-25T03:00:00.000Z",
"ratee_role": "seller",
"via_partner": "Salken"
}
]
}
}Errors
| Status | Code | Cause |
|---|---|---|
404 | NOT_FOUND | No active link with this ref |
| Plus the errors every Platform route can answer |
Show unclaimed profiles (dashboard)
Turns show_unclaimed_profiles on or off. While it is off (the default), an account you created stays off rekberpay.com/u until its owner claims it. Turning it on needs the consent statement ticked in the dashboard (consent: true and the current consent_version), and shows those accounts at once. Turning it off needs nothing and hides them again at once. Claimed accounts always follow the normal public rules, and sandbox accounts never show. Every change is recorded with who made it, and RekberPay may change it too. In the dashboard this is Settings, Public profiles, Show public profiles for accounts not claimed yet.
- Auth: A dashboard session of a workspace member with the
adminrole or higher. Workspace API keys get403 SESSION_REQUIRED: a person must tick the statement. - Scope: None. The member's role must allow
admin. - Rate limit: 60 a minute per member.
- Idempotency: Optional from the dashboard. A retry with the same
Idempotency-Keyand body returns the first answer.
Body (JSON)
| Name | Type | Required | Description |
|---|---|---|---|
show_unclaimed_profiles | boolean | Yes | true to show unclaimed accounts you created, false to hide them |
consent | boolean | To turn on | true: the consent statement was ticked |
consent_version | string | To turn on | The statement version shown, today 2026-10-20 |
Example request
curl -X PUT "https://api.rekberpay.com/v1/enterprise/platform/settings/profile-visibility" \
-H "Cookie: $REKBERPAY_DASHBOARD_SESSION" \
-H "X-CSRF-Token: $REKBERPAY_CSRF_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"show_unclaimed_profiles": true,
"consent": true,
"consent_version": "2026-10-20"
}'// From the dashboard in the browser: the session cookie goes along.
const res = await fetch('https://api.rekberpay.com/v1/enterprise/platform/settings/profile-visibility', {
method: 'PUT',
credentials: 'include',
headers: {
'Content-Type': 'application/json',
'X-CSRF-Token': csrfToken,
},
body: JSON.stringify({
show_unclaimed_profiles: true,
consent: true,
consent_version: '2026-10-20',
}),
});
const data = await res.json();import os
import requests
resp = requests.put(
"https://api.rekberpay.com/v1/enterprise/platform/settings/profile-visibility",
headers={
"Cookie": os.environ["REKBERPAY_DASHBOARD_SESSION"],
"X-CSRF-Token": os.environ["REKBERPAY_CSRF_TOKEN"],
},
json={
"show_unclaimed_profiles": True,
"consent": True,
"consent_version": "2026-10-20",
},
timeout=30,
)
print(resp.status_code, resp.json())<?php
$ch = curl_init('https://api.rekberpay.com/v1/enterprise/platform/settings/profile-visibility');
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'PUT',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Cookie: ' . getenv('REKBERPAY_DASHBOARD_SESSION'),
'X-CSRF-Token: ' . getenv('REKBERPAY_CSRF_TOKEN'),
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => json_encode([
'show_unclaimed_profiles' => true,
'consent' => true,
'consent_version' => '2026-10-20',
]),
]);
$response = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
curl_close($ch);Example response
200 OK: your settings, as Get your Platform settings answers them.
{
"success": true,
"data": {
"enabled": true,
"frozen": false,
"show_unclaimed_profiles": true,
"show_unclaimed_consent_at": "2026-10-25T03:00:00.000Z",
"show_unclaimed_consent_version": "2026-10-20",
"show_unclaimed_changed_by": "Rina Wulandari"
}
}Errors
| Status | Code | Cause |
|---|---|---|
400 | INVALID_BODY | show_unclaimed_profiles is missing or not a boolean |
403 | SESSION_REQUIRED | A workspace API key: change this in the dashboard |
403 | FORBIDDEN | Your workspace role is below admin |
422 | CONSENT_REQUIRED | Turning on without consent: true and the current consent_version |
| Plus the errors every Platform route can answer |
Webhook events
Platform mode adds these 19 new events to the enterprise webhooks. They come only from Platform activity, so only a workspace that uses Platform receives them. Payloads name users by partner_user_ref (buyer_ref, seller_ref and partner_user_refs on order events, rater_ref and ratee_ref on rating events), never by internal user id.
| Event | When |
|---|---|
platform.user.linked | You created an account, or a person accepted your link request |
platform.user.consent_declined | A person declined your link request |
platform.user.claimed | A person claimed an account you created |
platform.user.unlinked | A link was revoked: by you, by the person, or because the person became RekberPay staff |
platform.user.kyc_updated | A linked person's KYC status changed |
platform.user.balance_scope_changed | The person changed how much of their balance you may manage (balance_scope) |
bank_account.added | A payout bank account was added for a person (only the last 4 digits are sent) |
milestone.paid | The buyer paid a milestone |
milestone.shipped | A milestone was shipped |
milestone.received | The buyer confirmed receipt on RekberPay |
milestone.release_pending | You confirmed receipt; the release waits confirm_delay_hours |
milestone.auto_released | The automatic release paid the seller |
milestone.extended | The automatic release was extended |
withdrawal.requested | A withdrawal was created |
withdrawal.settled | A withdrawal was paid out |
withdrawal.failed | A withdrawal was rejected; the money is back in the balance |
rating.created | A side of a deal rated the other side (by you or on RekberPay). Refs only: no stars or review until it publishes |
rating.published | A rating became public, with stars, review and published_at. Sent again after RekberPay edits or shows a hidden rating, so upsert by rating_id |
rating.hidden | RekberPay hid or removed a rating. No reason is sent |
Marketplace deals also send deal.created, deal.cancelled, milestone.released (a release by the buyer, after your confirm, or by a dispute decision), dispute.opened, dispute.evidence_added and dispute.resolved (with outcome: release, refund, split or resume). Their marketplace payloads are in the list below.
What every payload has
These events use the same envelope, signature and delivery and retry rules as every other enterprise webhook. Ignore keys you do not know: RekberPay may add fields.
data.nextholds the fields listed for the event, plus three keys on every event:account_id(your workspace id),key_id(the workspace API key that made the call,nullwhen a person on RekberPay, RekberPay staff or the worker acted) andis_test(truefor sandbox data).data.previousisnull, or holds only the one or two keys the event moved from, named under each event.- Money is whole rupiah.
- People appear only as your own refs. There is never a RekberPay user id, an email, an ID number or a full bank account number in a payload.
entity_typeandentity_idname what changed:platform_user(the id is the linkidthe API returns for the user),milestone,deal,withdrawalorrating.deal_idis set when the event belongs to a deal, andamountcarries the milestone, deal or withdrawal amount where the event has one.livemodefollows the webhook envelope: drop events withlivemode: falsein production.
The deal parties on an order event are buyer_ref, seller_ref and partner_order_ref (your order id, or null when you sent none). deal_status is the deal's status after the change, as in Get a deal.
Users and bank accounts
platform.user.linked
entity_type platform_user. You created an account, or a person accepted your link request. data.previous holds link_status: "pending_consent" after an accepted request.
| Field | Type | Meaning |
|---|---|---|
partner_user_ref | string | Your ref for the person |
link_status | string | Always active |
origin | string | created (you made the account) or linked (the person already had one) |
balance_scope | string | partner_funds, or full_balance when the person allowed you to manage their whole balance |
claimed | boolean | true for a person who confirmed the link, false for an account you made that is not claimed yet |
platform.user.consent_declined
A person declined your link request. data.previous holds link_status: "pending_consent".
| Field | Type | Meaning |
|---|---|---|
partner_user_ref | string | Your ref for the person |
link_status | string | Always revoked |
revoked_by | string | Always user |
platform.user.claimed
A person claimed an account you created. data.previous holds claimed: false.
| Field | Type | Meaning |
|---|---|---|
partner_user_ref | string | Your ref for the person |
claimed | boolean | Always true |
platform.user.unlinked
A link was revoked. data.previous holds link_status: "active".
| Field | Type | Meaning |
|---|---|---|
partner_user_ref | string | Your ref for the person |
link_status | string | Always revoked |
revoked_by | string | partner (you unlinked), user (the person removed you on RekberPay) or system (RekberPay unlinked them, for example because the person became RekberPay staff) |
platform.user.kyc_updated
A linked person's KYC status changed. data.previous holds the old kyc_status.
| Field | Type | Meaning |
|---|---|---|
partner_user_ref | string | Your ref for the person |
kyc_status | string | The new status, as kyc_status on Get a user |
submitted_by | string | partner only when you sent the KYC (the status is then pending). Absent on a review result |
platform.user.balance_scope_changed
The person changed how much of their balance you may manage. Today a person can only lower it, in Pengaturan > Partner Terhubung (Settings, Connected partners). data.previous holds balance_scope: "full_balance". After this, from: "main" withdrawals answer 404.
| Field | Type | Meaning |
|---|---|---|
partner_user_ref | string | Your ref for the person |
balance_scope | string | The new scope, partner_funds or full_balance |
changed_by | string | Always user |
bank_account.added
A payout bank account was added for a person, by you or by the person on RekberPay. Only the last 4 digits are sent.
| Field | Type | Meaning |
|---|---|---|
partner_user_ref | string | Your ref for the person |
bank_account_id | string | The bank account id, as in Add a bank account |
bank_code | string | The bank code, for example BCA |
account_last4 | string | The last 4 digits of the account number |
cooling_off_hours | integer | Hours before the account can receive a withdrawal |
Orders
deal.created
entity_type deal, amount the total. An order was created.
| Field | Type | Meaning |
|---|---|---|
reference | string | RekberPay's deal reference |
partner_order_ref, buyer_ref, seller_ref | string or null | The parties |
milestones | integer | Number of milestones |
total_amount | integer | Sum of the milestone amounts |
platform_fee | integer | The whole fee |
buyer_fee, seller_fee | integer | Each side's share of the fee |
fee_bearer | string | buyer, seller or split |
status | string | The deal status |
deal.cancelled
entity_type deal. You cancelled an order before payment. data.previous holds the old status.
| Field | Type | Meaning |
|---|---|---|
status | string | The deal status after the cancel |
partner_order_ref, buyer_ref, seller_ref | string or null | The parties |
milestone.paid
entity_type milestone, amount the milestone amount. The buyer paid. data.previous holds status: "awaiting_payment".
| Field | Type | Meaning |
|---|---|---|
status | string | Always paid |
partner_order_ref, buyer_ref, seller_ref | string or null | The parties |
amount | integer | The milestone amount |
buyer_fee | integer | The buyer's fee share |
funding_amount | integer | What the buyer paid: amount plus buyer_fee plus payment_method_fee |
payment_method_fee | integer | The payment method's own fee |
funding_method | string | qris, va, ewallet, wire, manual, sandbox or cstore (paid at an Alfamart or Indomaret cashier). New values can be added: treat an unknown one as a valid funding method. |
deal_status | string | The deal status after the payment |
milestone.shipped
The milestone was shipped, by you or by the seller on RekberPay. data.previous holds the old status.
| Field | Type | Meaning |
|---|---|---|
status | string | Always shipped |
partner_order_ref, buyer_ref, seller_ref | string or null | The parties |
courier | string | The courier |
tracking_number | string or null | The tracking number, if one was sent |
auto_release_at | string or null | When the automatic release runs (ISO 8601), null when there is no clock |
deal_status | string | The deal status |
milestone.received
The buyer confirmed receipt on RekberPay. The release follows at once, as milestone.released. data.previous holds status: "shipped".
| Field | Type | Meaning |
|---|---|---|
status | string | Always received |
partner_order_ref, buyer_ref, seller_ref | string or null | The parties |
received_by | string | Always buyer |
milestone.release_pending
You confirmed receipt for the buyer. The money waits for the buyer's dispute window. data.previous holds the old status.
| Field | Type | Meaning |
|---|---|---|
status | string | Always release_pending |
partner_order_ref, buyer_ref, seller_ref | string or null | The parties |
received_by | string | Always partner |
release_at | string | When the release runs (ISO 8601) |
confirm_delay_hours | integer | The wait, 6 hours by default |
deal_status | string | The deal status |
milestone.released
The seller was paid, after the buyer confirmed, after your confirm, or after a dispute decision. data.previous holds the old status.
| Field | Type | Meaning |
|---|---|---|
status | string | Always released |
partner_order_ref, buyer_ref, seller_ref | string or null | The parties |
received_by | string | buyer, partner (your confirm), auto or admin (a dispute decision) |
amount | integer | The milestone amount |
buyer_fee, seller_fee | integer | Each side's fee share |
payout_amount | integer | What the seller's partner balance received: amount minus seller_fee |
fee_revenue | integer | The fee RekberPay kept, buyer_fee plus seller_fee |
deal_status | string | The deal status |
milestone.auto_released
The automatic release paid the seller because nobody acted in time. It has the same fields as milestone.released, with received_by always auto: status, partner_order_ref, buyer_ref, seller_ref, received_by, amount, buyer_fee, seller_fee, payout_amount, fee_revenue and deal_status.
milestone.extended
The buyer extended the automatic release, through you or on RekberPay. No amount. data.previous holds the old auto_release_at and extend_count.
| Field | Type | Meaning |
|---|---|---|
status | string | Always shipped |
partner_order_ref, buyer_ref, seller_ref | string or null | The parties |
auto_release_at | string | The new release time (ISO 8601) |
extend_count | integer | How many times it was extended so far |
added_hours | integer | Hours this extension added |
Disputes
dispute.opened
entity_type milestone, amount the milestone amount. data.previous holds the old status, and auto_release_at and release_at as they were.
| Field | Type | Meaning |
|---|---|---|
status | string | Always disputed |
reason | string | The reason that was sent |
raised_by | string | buyer or seller |
via | string | api (you), buyer (on RekberPay) or release_notice (the buyer's page after your confirm) |
partner_order_ref, buyer_ref, seller_ref | string or null | The parties |
partner_user_refs | string[] | The refs of the buyer and the seller |
release_cancelled | boolean | true when the dispute stopped a pending confirm of yours |
timer_remaining_seconds | integer or null | Seconds left on the clock when it paused, restored on a resume |
deal_status | string | The deal status |
dispute.evidence_added
Evidence was added to an open dispute by you. RekberPay staff see the files; they are never in the payload.
| Field | Type | Meaning |
|---|---|---|
status | string | Always disputed |
note | string | The statement |
document_ids | string[] | Ids of the files that were stored |
partner_order_ref, buyer_ref, seller_ref | string or null | The parties |
partner_user_refs | string[] | The refs of the buyer and the seller |
dispute.resolved
RekberPay's mediation team decided. data.previous holds status: "disputed". The fields depend on outcome. A release also sends milestone.released.
| Field | Outcomes | Meaning |
|---|---|---|
outcome | all | release, refund, split or resume |
status | all | The milestone status after: released, refunded, released or shipped |
note | all | The mediation team's note |
partner_order_ref, buyer_ref, seller_ref | all | The parties |
partner_user_refs | all | The refs of the buyer and the seller |
deal_status | all | The deal status |
payout_amount | release | What the seller's partner balance received: amount minus seller_fee |
refund_amount | refund, split | The principal that goes back to the buyer. A full refund is the whole milestone amount, the same number as refund_amount on the milestone |
buyer_fee_refunded | refund, split | The buyer's fee share that goes back with it. The payment method fee is never refunded |
buyer_credit | refund, split | What the buyer's partner balance received: refund_amount plus buyer_fee_refunded |
seller_credit | split | What the seller's partner balance received |
fee_revenue | split | The fee RekberPay kept |
auto_release_at | resume | The restarted release time (ISO 8601), null when there was no clock |
timer_remaining_seconds | resume | The seconds that were left, null when there was no clock |
A split, as it arrives:
{
"event": "dispute.resolved",
"entity_type": "milestone",
"amount": 1000000,
"livemode": false,
"data": {
"previous": { "status": "disputed" },
"next": {
"outcome": "split",
"status": "released",
"note": "Part of the item arrived damaged.",
"partner_order_ref": "ORD-55012",
"buyer_ref": "buyer-1001",
"seller_ref": "seller-2001",
"partner_user_refs": ["buyer-1001", "seller-2001"],
"deal_status": "completed",
"refund_amount": 400000,
"buyer_fee_refunded": 4000,
"buyer_credit": 404000,
"seller_credit": 594000,
"fee_revenue": 12000,
"account_id": "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa",
"key_id": null,
"is_test": true
}
}
}Withdrawals
withdrawal.requested
entity_type withdrawal, amount the withdrawal amount. A withdrawal was created, by you, or by the person from their partner balance on RekberPay while linked to you.
| Field | Type | Meaning |
|---|---|---|
partner_user_ref | string or null | Your ref for the person. null only when they are no longer linked to you when the result lands |
status | string | pending, or pending_review when a second RekberPay approver must approve it first |
from | string | partner (the partner balance) or main (the person's own RekberPay balance) |
amount | integer | The amount, fee included |
fee | integer | The withdrawal fee charged, set by its bank rail (see Withdrawal fees) |
net_amount | integer | What the bank receives: amount minus fee |
bank_account_id | string or null | The payout bank account |
withdrawal.settled
A withdrawal was paid out. The fields are those of withdrawal.requested (partner_user_ref, status, from, amount, fee, net_amount, bank_account_id), with status always completed. data.previous holds the old status when the change came from RekberPay's payout team.
withdrawal.failed
A withdrawal was rejected and the money is back in the balance it came from. It has the same fields (partner_user_ref, status, from, amount, fee, net_amount and bank_account_id), with status always rejected.
Ratings
rating.created
entity_type rating, deal_id set. A side of a deal rated the other side, through you or on RekberPay. It is private: no stars and no review.
| Field | Type | Meaning |
|---|---|---|
rating_id | string | The rating id. Store ratings by it |
deal_id | string | The deal |
partner_order_ref | string or null | Your order id |
rater_role | string | buyer or seller: the side that rated |
rater_ref, ratee_ref | string or null | The refs of who rated and who was rated. null when the person is not linked to you |
submitted_by | string | partner (you) or user (on RekberPay) |
published | boolean | Always false |
rating.published
The rating became public. It is sent again after RekberPay corrects a rating or shows a hidden one again, so upsert by rating_id. It has every field of rating.created (rating_id, deal_id, partner_order_ref, rater_role, rater_ref, ratee_ref, submitted_by), with published always true, and:
| Field | Type | Meaning |
|---|---|---|
stars | integer | 1 to 5 |
review | string or null | The review text |
published_at | string | When it became public (ISO 8601) |
rating.hidden
RekberPay hid or removed a rating. The reason is never sent.
| Field | Type | Meaning |
|---|---|---|
rating_id | string | The rating id |
deal_id | string | The deal |
partner_order_ref | string or null | Your order id |
ratee_ref | string or null | The ref of the person who was rated |
hidden_at | string | When it was hidden (ISO 8601) |
Error codes
Every Platform code in one place. Each endpoint above lists the ones it can answer.
| Code | Status | Meaning |
|---|---|---|
ACCOUNT_NOT_ACTIVE | 409 | The user's account is closing, so it cannot withdraw |
ALREADY_RATED | 409 | This side already rated this deal |
BANK_ACCOUNT_COOLING_OFF | 409 | The bank account is in its cooling-off. See usable_after |
BANK_ACCOUNT_EXISTS | 409 | This bank account is already saved for the user |
BANK_ACCOUNT_LIMIT | 409 | The user has 10 payout bank accounts with you |
BANK_ACCOUNT_NOT_FOUND | 404 | No such bank account for the user |
CHECKOUT_CHANGED | 409 | Buyer page: the order changed after the checkout link was made |
CHECKOUT_USED | 409 | Buyer page: this checkout link already started a payment |
CONSENT_REQUIRED | 422 | Showing unclaimed profiles needs the consent statement ticked |
DAILY_CAP_REACHED | 409 | The deal would pass your daily cap |
DEAL_CREATE_BUSY | 409 | Another deal is being created at the same moment |
DUPLICATE_ORDER_REF | 409 | A deal with this partner_order_ref exists |
EVIDENCE_LIMIT_REACHED | 409 | The dispute holds 50 files |
EXTEND_DISABLED | 409 | Extensions are switched off |
EXTEND_LIMIT_REACHED | 409 | The milestone was extended the most times allowed |
FEE_EXCEEDS_AMOUNT | 422 | Deals: a milestone is too small for the seller's fee share. Withdrawals: the fee is not lower than the amount |
FILE_TOO_LARGE | 413 | A file is over 10 MB |
INSUFFICIENT_BALANCE | 422 | The balance is too low for the withdrawal |
INVALID_AMOUNT | 422 | The withdrawal amount is outside the limits |
INVALID_BODY | 400 | The body is missing a field or a field is not valid |
INVALID_FEE_ALLOCATION | 400 | fee_bearer and fee_split_buyer_pct do not fit together |
INVALID_NIK | 400 | kyc_nik is not a valid NIK |
INVALID_QUERY | 400 | A list query value is not valid |
INVALID_STATE | 409 | The deal or milestone is not in a status that allows this |
KYB_REQUIRED | 403 | A Live request of a workspace without verified KYB |
KYC_LOCKED | 409 | A KYC is already pending or verified |
KYC_NOT_PENDING | 409 | Sandbox: no pending KYC to simulate |
KYC_SUBMIT_LIMITED | 429 | Too many KYC submissions for now |
LIMIT_EXCEEDED | 422 | The buyer or seller is over the non-KYC limits (party, reason) |
LINK_EXPIRED | 410 | Buyer and user pages: the link is invalid, expired or used |
MAIL_LIMIT_REACHED | 429 | Live: this user or address already got as many emails as allowed today (Retry-After) |
NAME_LOCKED | 409 | The user's name can no longer be changed through the API |
NAME_MISMATCH | 422 | A bank holder name or KYC name does not match the user's name |
NIK_IN_USE | 409 | The NIK verifies another RekberPay account |
NO_ACTIVE_TIMER | 409 | The milestone has no running automatic release |
NOT_DISPUTED | 409 | The dispute is not open |
NOT_EXTENDABLE | 409 | Only a shipped milestone can be extended |
NOT_RATEABLE | 409 | The deal is not completed, so it cannot be rated yet (or every paid part was refunded) |
PAYMENT_IN_PROGRESS | 409 | A checkout payment can still land on the deal |
PLATFORM_FROZEN | 423 | RekberPay paused your Platform access |
REF_IN_USE | 409 | The partner_user_ref is linked to another email |
RISK_BLOCKED | 403 | RekberPay screening refused the withdrawal |
SAME_PARTY | 422 | The buyer and the seller are the same person |
SANDBOX_EMAIL_REQUIRED | 422 | A sandbox user needs a test domain email |
SANDBOX_MAIN_BALANCE | 422 | Sandbox withdrawals come from the partner balance only |
SANDBOX_TEST_NIK_REQUIRED | 422 | Sandbox KYC needs a test NIK that ends in 0000 |
SCOPE_NOT_ALLOWED | 400 | Creating a key with a Platform scope your terms do not allow |
SESSION_REQUIRED | 403 | This setting is changed in the dashboard only, never with an API key |
TEST_DEAL | 409 | Buyer page: a sandbox order cannot take a real payment |
TOO_MANY_CODES | 429 | Claim page: too many codes were requested |
TRY_AGAIN | 409 | Another change to the same user was saved at the same moment. Nothing was done |
TYPE_NOT_ALLOWED | 400 | The file type is not allowed |
TYPE_MISMATCH, IMAGE_DECODE_FAILED, PDF_* | 400 | The file content is not what it claims, is damaged, or a PDF cannot be cleaned |
QUOTA_EXCEEDED | 429 | The daily upload limit is reached |
STORAGE_FAILED | 500 | A file could not be stored. Nothing changed |
PDF_CHECK_UNAVAILABLE | 503 | PDFs cannot be checked right now |
WITHDRAWAL_FAILED | 500 | The withdrawal could not be saved. Nothing was charged |
USER_ALREADY_LINKED | 409 | The person is linked to you under another ref |
USER_DECLINED_RECENTLY | 429 | Live: the person declined or ended your link less than 7 days ago (Retry-After) |
USER_MANAGED_BY_OWNER | 409 | The person manages their own account |
USER_NOT_AVAILABLE | 422 or 409 | The person cannot take part, or RekberPay ended your link (422), or a buyer is no longer linked (409) |
USER_NOT_FOUND | 404 | Deal create: no active user with this ref |
The codes every route can answer are in Errors every Platform route can answer.