Disputes API
A dispute freezes the escrow while RekberPay looks at the case. Either party can open one while the escrow is paid, sent or received. Both parties and RekberPay staff talk in the dispute thread; RekberPay then releases the money to the seller, refunds the buyer or holds it. Resolution is a staff action, so there is no endpoint for it here.
The Handling disputes guide shows the whole flow.
Endpoints
| Method | Path | Endpoint | Auth |
|---|---|---|---|
POST | /v1/escrows/:id/dispute | Open a dispute | Key or session |
GET | /v1/disputes | List your disputes | Key or session |
GET | /v1/disputes/:id | Get a dispute | Key or session |
GET | /v1/disputes/:id/messages | List dispute messages | Key or session |
POST | /v1/disputes/:id/messages | Send a dispute message | Key or session |
Open a dispute
Opens a dispute and moves the escrow to disputed. The other party and RekberPay are notified.
- Auth: A personal API key (
Authorization: Bearer <key>) or a session cookie withX-CSRF-Tokenon writes. - Who can call: The buyer or the seller, while the escrow is
paid,sentorreceived. - Rate limit: 300 requests a minute per user, shared by every route.
- Idempotency: Not supported. A second dispute on the same escrow answers
409 NOT_DISPUTABLE, so a retry cannot open two.
Path parameters
| Name | Type | Description |
|---|---|---|
id | UUID | The escrow ID |
Body (JSON)
| Name | Type | Required | Description |
|---|---|---|---|
reason_category | string | Yes | item_not_received, item_not_as_described, seller_unresponsive or other |
description | string | Yes | What happened, 1 to 3,000 characters |
Example request
curl -X POST https://api.rekberpay.com/v1/escrows/55555551-5555-4555-8555-555555555555/dispute \
-H "Authorization: Bearer $REKBERPAY_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"reason_category": "item_not_as_described",
"description": "Logo yang dikirim tidak sesuai brief: warna dan font berbeda dari yang disepakati."
}'const res = await fetch('https://api.rekberpay.com/v1/escrows/55555551-5555-4555-8555-555555555555/dispute', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.REKBERPAY_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
reason_category: "item_not_as_described",
description: "Logo yang dikirim tidak sesuai brief: warna dan font berbeda dari yang disepakati."
}),
});
const data = await res.json();import os
import requests
resp = requests.post(
"https://api.rekberpay.com/v1/escrows/55555551-5555-4555-8555-555555555555/dispute",
headers={
"Authorization": f"Bearer {os.environ['REKBERPAY_API_KEY']}",
},
json={
"reason_category": "item_not_as_described",
"description": "Logo yang dikirim tidak sesuai brief: warna dan font berbeda dari yang disepakati.",
},
timeout=30,
)
print(resp.status_code, resp.json())<?php
$ch = curl_init('https://api.rekberpay.com/v1/escrows/55555551-5555-4555-8555-555555555555/dispute');
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('REKBERPAY_API_KEY'),
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => json_encode([
'reason_category' => 'item_not_as_described',
'description' => 'Logo yang dikirim tidak sesuai brief: warna dan font berbeda dari yang disepakati.',
]),
]);
$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": "77777771-7777-4777-8777-777777777777",
"escrow_id": "55555551-5555-4555-8555-555555555555",
"initiator_id": "11111110-1111-4111-8111-111111111111",
"reason_category": "item_not_as_described",
"description": "Logo yang dikirim tidak sesuai brief: warna dan font berbeda dari yang disepakati.",
"status": "open",
"priority": "normal",
"resolution_note": null,
"resolved_by": null,
"resolved_at": null,
"created_at": "2026-10-13T14:23:18.823Z",
"updated_at": "2026-10-13T14:23:18.823Z"
}
}Errors
| Status | Code | Cause |
|---|---|---|
400 | INVALID_BODY | A field failed validation |
403 | FORBIDDEN | Only the buyer or the seller can open a dispute |
404 | NOT_FOUND | No escrow with this ID |
409 | NOT_DISPUTABLE | The escrow is not paid, sent or received |
409 | DISPUTE_EXISTS | An open dispute already exists |
409 | ESCROW_MOVED | The escrow changed status while the call ran |
409 | ACCOUNT_NOT_ACTIVE | One of the parties is no longer active |
401 | UNAUTHORIZED, SESSION_EXPIRED, INVALID_TOKEN | No session or API key, the session expired, or the key is wrong or revoked |
401 | WORKSPACE_KEY_ONLY | A workspace key (rpe_...) was sent. Those only work under /v1/enterprise |
403 | CSRF_FAILED | Session call without the matching X-CSRF-Token header |
403 | ACCOUNT_INACTIVE, SUSPENDED | The account is closed, being deleted or suspended |
429 | RATE_LIMITED | Over the rate limit. Wait for Retry-After seconds |
A real NOT_DISPUTABLE answer:
409 Conflict
{
"success": false,
"error": {
"code": "NOT_DISPUTABLE",
"message": "Cannot dispute escrow in state: disputed. Must be: paid, sent, received"
}
}List your disputes
Disputes on escrows where you are the buyer or the seller, newest first, with the escrow's item, amount, status and both parties.
- Auth: A personal API key (
Authorization: Bearer <key>) or a session cookie withX-CSRF-Tokenon writes. - Rate limit: 300 requests a minute per user, shared by every route.
- Idempotency: Not needed. Reading has no side effects.
Query parameters
| Name | Type | Required | Description |
|---|---|---|---|
status | string | No | Only this dispute status, for example open |
page | integer | No | Default 1 |
limit | integer | No | 1 to 50. Default 20 |
Example request
curl "https://api.rekberpay.com/v1/disputes?limit=5" \
-H "Authorization: Bearer $REKBERPAY_API_KEY"const res = await fetch('https://api.rekberpay.com/v1/disputes?limit=5', {
headers: {
Authorization: `Bearer ${process.env.REKBERPAY_API_KEY}`,
},
});
const data = await res.json();import os
import requests
resp = requests.get(
"https://api.rekberpay.com/v1/disputes?limit=5",
headers={
"Authorization": f"Bearer {os.environ['REKBERPAY_API_KEY']}",
},
timeout=30,
)
print(resp.status_code, resp.json())<?php
$ch = curl_init('https://api.rekberpay.com/v1/disputes?limit=5');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('REKBERPAY_API_KEY'),
],
]);
$response = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
curl_close($ch);Example response
200 OK
{
"success": true,
"data": [
{
"id": "77777771-7777-4777-8777-777777777777",
"escrow_id": "55555551-5555-4555-8555-555555555555",
"initiator_id": "11111110-1111-4111-8111-111111111111",
"reason_category": "item_not_as_described",
"description": "Logo yang dikirim tidak sesuai brief: warna dan font berbeda dari yang disepakati.",
"status": "open",
"priority": "normal",
"resolution_note": null,
"resolved_by": null,
"resolved_at": null,
"created_at": "2026-10-13T14:23:18.823Z",
"updated_at": "2026-10-13T14:23:18.823Z",
"item_name": "Jasa desain logo toko",
"amount": "300000.00",
"escrow_status": "disputed",
"buyer_id": "11111110-1111-4111-8111-111111111111",
"seller_id": "44444440-4444-4444-8444-444444444444",
"buyer_email": "[email protected]",
"buyer_full_name": "Rina Wijaya",
"seller_email": null,
"seller_full_name": "Budi Santoso"
}
],
"pagination": {
"page": 1,
"limit": 5,
"total": "1"
}
}Errors
| Status | Code | Cause |
|---|---|---|
401 | UNAUTHORIZED, SESSION_EXPIRED, INVALID_TOKEN | No session or API key, the session expired, or the key is wrong or revoked |
401 | WORKSPACE_KEY_ONLY | A workspace key (rpe_...) was sent. Those only work under /v1/enterprise |
403 | ACCOUNT_INACTIVE, SUSPENDED | The account is closed, being deleted or suspended |
429 | RATE_LIMITED | Over the rate limit. Wait for Retry-After seconds |
Get a dispute
- Auth: A personal API key (
Authorization: Bearer <key>) or a session cookie withX-CSRF-Tokenon writes. - Who can call: The buyer, the seller or a RekberPay admin.
- Rate limit: 300 requests a minute per user, shared by every route.
- Idempotency: Not needed. Reading has no side effects.
Path parameters
| Name | Type | Description |
|---|---|---|
id | UUID | The dispute ID |
Example request
curl https://api.rekberpay.com/v1/disputes/77777771-7777-4777-8777-777777777777 \
-H "Authorization: Bearer $REKBERPAY_API_KEY"const res = await fetch('https://api.rekberpay.com/v1/disputes/77777771-7777-4777-8777-777777777777', {
headers: {
Authorization: `Bearer ${process.env.REKBERPAY_API_KEY}`,
},
});
const data = await res.json();import os
import requests
resp = requests.get(
"https://api.rekberpay.com/v1/disputes/77777771-7777-4777-8777-777777777777",
headers={
"Authorization": f"Bearer {os.environ['REKBERPAY_API_KEY']}",
},
timeout=30,
)
print(resp.status_code, resp.json())<?php
$ch = curl_init('https://api.rekberpay.com/v1/disputes/77777771-7777-4777-8777-777777777777');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('REKBERPAY_API_KEY'),
],
]);
$response = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
curl_close($ch);Example response
200 OK
{
"success": true,
"data": {
"id": "77777771-7777-4777-8777-777777777777",
"escrow_id": "55555551-5555-4555-8555-555555555555",
"initiator_id": "11111110-1111-4111-8111-111111111111",
"reason_category": "item_not_as_described",
"description": "Logo yang dikirim tidak sesuai brief: warna dan font berbeda dari yang disepakati.",
"status": "open",
"priority": "normal",
"resolution_note": null,
"resolved_by": null,
"resolved_at": null,
"created_at": "2026-10-13T14:23:18.823Z",
"updated_at": "2026-10-13T14:23:18.823Z",
"item_name": "Jasa desain logo toko",
"amount": "300000.00",
"escrow_status": "disputed",
"buyer_id": "11111110-1111-4111-8111-111111111111",
"seller_id": "44444440-4444-4444-8444-444444444444",
"buyer_email": "[email protected]",
"buyer_full_name": "Rina Wijaya",
"seller_email": null,
"seller_full_name": "Budi Santoso"
}
}| Field | Type | Description |
|---|---|---|
status | string | open while RekberPay works on it, then the outcome |
reason_category | string | The reason given when it was opened |
resolution_note | string | null | RekberPay's note when it is resolved |
Errors
| Status | Code | Cause |
|---|---|---|
403 | FORBIDDEN | You are not a party |
404 | NOT_FOUND | No dispute with this ID |
401 | UNAUTHORIZED, SESSION_EXPIRED, INVALID_TOKEN | No session or API key, the session expired, or the key is wrong or revoked |
401 | WORKSPACE_KEY_ONLY | A workspace key (rpe_...) was sent. Those only work under /v1/enterprise |
403 | ACCOUNT_INACTIVE, SUSPENDED | The account is closed, being deleted or suspended |
429 | RATE_LIMITED | Over the rate limit. Wait for Retry-After seconds |
List dispute messages
The dispute thread, oldest first. Each message says who sent it (sender_participant_role: buyer, seller or admin). Send the newest created_at as after and its id as after_id when polling. sender_email is filled only on your own messages.
- Auth: A personal API key (
Authorization: Bearer <key>) or a session cookie withX-CSRF-Tokenon writes. - Who can call: The buyer, the seller or a RekberPay admin.
- Rate limit: 300 requests a minute per user, shared by every route.
- Idempotency: Not needed. Reading has no side effects.
Path parameters
| Name | Type | Description |
|---|---|---|
id | UUID | The dispute ID |
Query parameters
| Name | Type | Required | Description |
|---|---|---|---|
limit | integer | No | 1 to 200. Default 100 |
after | string | No | ISO 8601 time. Only messages after it |
after_id | string | No | The id of the message at after. With it, the cursor is the pair (created_at, id), so two messages written in the same millisecond are never missed |
before | string | No | ISO 8601 time. Only messages before it |
Example request
curl "https://api.rekberpay.com/v1/disputes/77777771-7777-4777-8777-777777777777/messages?limit=20" \
-H "Authorization: Bearer $REKBERPAY_API_KEY"const res = await fetch('https://api.rekberpay.com/v1/disputes/77777771-7777-4777-8777-777777777777/messages?limit=20', {
headers: {
Authorization: `Bearer ${process.env.REKBERPAY_API_KEY}`,
},
});
const data = await res.json();import os
import requests
resp = requests.get(
"https://api.rekberpay.com/v1/disputes/77777771-7777-4777-8777-777777777777/messages?limit=20",
headers={
"Authorization": f"Bearer {os.environ['REKBERPAY_API_KEY']}",
},
timeout=30,
)
print(resp.status_code, resp.json())<?php
$ch = curl_init('https://api.rekberpay.com/v1/disputes/77777771-7777-4777-8777-777777777777/messages?limit=20');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('REKBERPAY_API_KEY'),
],
]);
$response = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
curl_close($ch);Example response
200 OK
{
"success": true,
"data": [
{
"id": "99999991-9999-4999-8999-999999999999",
"dispute_id": "77777771-7777-4777-8777-777777777777",
"sender_id": "11111110-1111-4111-8111-111111111111",
"body": "Saya lampirkan brief awal dan hasil yang diterima.",
"attachment_url": "https://api.rekberpay.com/uploads/escrow/1791901398283_862e8cf475cf7693efed4855a91995d0_7e10e097a2087a40e3e8e3b659df3f65.png?exp=1791979200000&sig=c0ffee5b1a9e3d7f4a2c8e6b0d9f1a3c5e7b9d1f3a5c7e9b1d3f5a7c9e1b3d5",
"created_at": "2026-10-13T14:23:18.835Z",
"sender_email": "[email protected]",
"sender_full_name": "Rina Wijaya",
"sender_role": "user",
"sender_participant_role": "buyer"
},
{
"id": "aaaaaaa1-aaaa-4aaa-8aaa-aaaaaaaaaaaa",
"dispute_id": "77777771-7777-4777-8777-777777777777",
"sender_id": "44444440-4444-4444-8444-444444444444",
"body": "Revisi bisa saya kirim besok pagi.",
"attachment_url": null,
"created_at": "2026-10-13T14:23:18.841Z",
"sender_email": null,
"sender_full_name": "Budi Santoso",
"sender_role": "user",
"sender_participant_role": "seller"
}
]
}Errors
| Status | Code | Cause |
|---|---|---|
403 | FORBIDDEN | You are not a party |
404 | NOT_FOUND | No dispute with this ID |
401 | UNAUTHORIZED, SESSION_EXPIRED, INVALID_TOKEN | No session or API key, the session expired, or the key is wrong or revoked |
401 | WORKSPACE_KEY_ONLY | A workspace key (rpe_...) was sent. Those only work under /v1/enterprise |
403 | ACCOUNT_INACTIVE, SUSPENDED | The account is closed, being deleted or suspended |
429 | RATE_LIMITED | Over the rate limit. Wait for Retry-After seconds |
Send a dispute message
Adds a message and up to 10 evidence files. Upload files first with POST /v1/upload/escrow.
- Auth: A personal API key (
Authorization: Bearer <key>) or a session cookie withX-CSRF-Tokenon writes. - Who can call: The buyer, the seller or a RekberPay admin.
- Rate limit: 300 requests a minute per user, shared by every route.
- Idempotency: Not supported. A retry runs the call again.
Path parameters
| Name | Type | Description |
|---|---|---|
id | UUID | The dispute ID |
Body (JSON)
| Name | Type | Required | Description |
|---|---|---|---|
message | string | Yes | 1 to 5,000 characters |
evidence_urls | string[] | No | Up to 10 RekberPay upload URLs |
Example request
curl -X POST https://api.rekberpay.com/v1/disputes/77777771-7777-4777-8777-777777777777/messages \
-H "Authorization: Bearer $REKBERPAY_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"message": "Saya lampirkan brief awal dan hasil yang diterima.",
"evidence_urls": [
"https://api.rekberpay.com/uploads/escrow/1791901398283_862e8cf475cf7693efed4855a91995d0_7e10e097a2087a40e3e8e3b659df3f65.png"
]
}'const res = await fetch('https://api.rekberpay.com/v1/disputes/77777771-7777-4777-8777-777777777777/messages', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.REKBERPAY_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
message: "Saya lampirkan brief awal dan hasil yang diterima.",
evidence_urls: [
"https://api.rekberpay.com/uploads/escrow/1791901398283_862e8cf475cf7693efed4855a91995d0_7e10e097a2087a40e3e8e3b659df3f65.png"
]
}),
});
const data = await res.json();import os
import requests
resp = requests.post(
"https://api.rekberpay.com/v1/disputes/77777771-7777-4777-8777-777777777777/messages",
headers={
"Authorization": f"Bearer {os.environ['REKBERPAY_API_KEY']}",
},
json={
"message": "Saya lampirkan brief awal dan hasil yang diterima.",
"evidence_urls": [
"https://api.rekberpay.com/uploads/escrow/1791901398283_862e8cf475cf7693efed4855a91995d0_7e10e097a2087a40e3e8e3b659df3f65.png",
],
},
timeout=30,
)
print(resp.status_code, resp.json())<?php
$ch = curl_init('https://api.rekberpay.com/v1/disputes/77777771-7777-4777-8777-777777777777/messages');
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('REKBERPAY_API_KEY'),
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => json_encode([
'message' => 'Saya lampirkan brief awal dan hasil yang diterima.',
'evidence_urls' => [
'https://api.rekberpay.com/uploads/escrow/1791901398283_862e8cf475cf7693efed4855a91995d0_7e10e097a2087a40e3e8e3b659df3f65.png',
],
]),
]);
$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": "99999991-9999-4999-8999-999999999999",
"dispute_id": "77777771-7777-4777-8777-777777777777",
"sender_id": "11111110-1111-4111-8111-111111111111",
"body": "Saya lampirkan brief awal dan hasil yang diterima.",
"attachment_url": "https://api.rekberpay.com/uploads/escrow/1791901398283_862e8cf475cf7693efed4855a91995d0_7e10e097a2087a40e3e8e3b659df3f65.png?exp=1791979200000&sig=c0ffee5b1a9e3d7f4a2c8e6b0d9f1a3c5e7b9d1f3a5c7e9b1d3f5a7c9e1b3d5",
"created_at": "2026-10-13T14:23:18.835Z"
}
}Errors
| Status | Code | Cause |
|---|---|---|
400 | INVALID_BODY | Message missing or too long, or an evidence URL is not a RekberPay upload |
403 | FORBIDDEN | You are not a party |
404 | NOT_FOUND | No dispute with this ID |
401 | UNAUTHORIZED, SESSION_EXPIRED, INVALID_TOKEN | No session or API key, the session expired, or the key is wrong or revoked |
401 | WORKSPACE_KEY_ONLY | A workspace key (rpe_...) was sent. Those only work under /v1/enterprise |
403 | CSRF_FAILED | Session call without the matching X-CSRF-Token header |
403 | ACCOUNT_INACTIVE, SUSPENDED | The account is closed, being deleted or suspended |
429 | RATE_LIMITED | Over the rate limit. Wait for Retry-After seconds |