Enterprise approvals and payouts
After evidence is submitted, finance members decide on it. A milestone at or above the four-eyes threshold needs two different people to approve. An approved milestone is then released by finance to one to five verified payout instructions; each split becomes a payout (disbursement) that RekberPay settles. Money only ever leaves to a payout instruction that RekberPay has verified and signed. Auth, roles, the workspace header and the shared limits are explained in Enterprise overview.
Endpoints
| Method | Path | Endpoint | Auth |
|---|---|---|---|
GET | /v1/enterprise/approvals | List the approval queue | viewer |
GET | /v1/enterprise/approvals/:milestoneId | Get an approval | viewer |
POST | /v1/enterprise/approvals/:milestoneId/decide | Decide on evidence | finance |
POST | /v1/enterprise/deals/:id/milestones/:mid/release | Release a milestone | finance |
GET | /v1/enterprise/payouts | List payouts | viewer |
GET | /v1/enterprise/payout-instructions | List payout instructions | viewer |
GET | /v1/enterprise/payout-instructions/:id | Get a payout instruction | viewer |
POST | /v1/enterprise/payout-instructions | Add a payout instruction | finance |
PATCH | /v1/enterprise/payout-instructions/:id | Update a payout instruction | finance |
DELETE | /v1/enterprise/payout-instructions/:id | Delete a payout instruction | finance |
List the approval queue
Milestones waiting for a decision, oldest first. With status=decided, milestones decided in the last 30 days, newest first, with their current status.
- Auth: A workspace API key (
Authorization: Bearer rpe_...), a personal API key or a dashboard session. - Role: Any workspace member (
viewerand up). - 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 | decided for recent decisions. Leave it out for the open queue |
page | integer | No | Default 1 |
limit | integer | No | 1 to 50. Default 20 |
Example request
curl "https://api.rekberpay.com/v1/enterprise/approvals?limit=2" \
-H "Authorization: Bearer $REKBERPAY_WORKSPACE_KEY"const res = await fetch('https://api.rekberpay.com/v1/enterprise/approvals?limit=2', {
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/approvals?limit=2",
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/approvals?limit=2');
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": [
{
"milestone_id": "aaaaaaa5-aaaa-4aaa-8aaa-aaaaaaaaaaaa",
"deal_id": "bbbbbbb5-bbbb-4bbb-8bbb-bbbbbbbbbbbb",
"deal_reference": "DEAL-2026-0003",
"deal_title": "Electronic control boards, lot 7",
"milestone_title": "Goods in transit",
"status": "evidence_submitted",
"amount": 150000000,
"currency": "IDR",
"counterparty_name": "Harbourline Components Pte Ltd",
"risk_tier": "standard",
"evidence_summary": "Bill of lading HLCU-5521 and the export declaration for all 600 boards.",
"evidence_submitted_at": "2026-10-13T09:33:06.162Z",
"due_at": "2026-10-15T14:33:06.162Z",
"approvals_required": 2,
"approvals_done": 1,
"my_decision": "approved",
"submitted_by": {
"id": "88888884-8888-4888-8888-888888888888",
"name": "Sari Wibowo"
}
},
{
"milestone_id": "ccccccc5-cccc-4ccc-8ccc-cccccccccccc",
"deal_id": "ddddddd5-dddd-4ddd-8ddd-dddddddddddd",
"deal_reference": "DEAL-2026-0004",
"deal_title": "Cold-chain logistics, Jakarta to Surabaya",
"milestone_title": "QA inspection passed",
"status": "evidence_submitted",
"amount": 18500000,
"currency": "IDR",
"counterparty_name": "PT Sinar Logistik Nusantara",
"risk_tier": "standard",
"evidence_summary": "Temperature log for the full route, never warmer than -19C.",
"evidence_submitted_at": "2026-10-13T12:33:06.162Z",
"due_at": "2026-10-16T02:33:06.162Z",
"approvals_required": 1,
"approvals_done": 0,
"my_decision": null,
"submitted_by": {
"id": "88888884-8888-4888-8888-888888888888",
"name": "Sari Wibowo"
}
}
],
"pagination": {
"page": 1,
"limit": 2,
"total": 4,
"total_pages": 2
}
}| Field | Type | Description |
|---|---|---|
approvals_required | integer | 1, or 2 for four-eyes milestones |
approvals_done | integer | Approvals given in the current round |
my_decision | string | null | Your decision in this round, if any |
submitted_by | object | Who submitted the evidence. That person cannot decide |
Errors
| Status | Code | Cause |
|---|---|---|
401 | UNAUTHORIZED, SESSION_EXPIRED, INVALID_TOKEN | No session or key, the session expired, or the key is wrong or revoked |
400 | INVALID_ACCOUNT | X-Enterprise-Account is not a UUID |
403 | NO_WORKSPACE | You are not an active member of that workspace, or the key belongs to another one |
429 | RATE_LIMITED | Over the rate limit. Wait for Retry-After seconds |
Get an approval
Everything needed to decide: the queue item, the proposed payout with a masked account number, recent events (how_we_got_here), impact, the current round under decision and the evidence documents.
- Auth: A workspace API key (
Authorization: Bearer rpe_...), a personal API key or a dashboard session. - Role: Any workspace member (
viewerand up). - 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 |
|---|---|---|
milestoneId | UUID | The milestone ID |
Example request
curl https://api.rekberpay.com/v1/enterprise/approvals/fffffff4-ffff-4fff-8fff-ffffffffffff \
-H "Authorization: Bearer $REKBERPAY_WORKSPACE_KEY"const res = await fetch('https://api.rekberpay.com/v1/enterprise/approvals/fffffff4-ffff-4fff-8fff-ffffffffffff', {
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/approvals/fffffff4-ffff-4fff-8fff-ffffffffffff",
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/approvals/fffffff4-ffff-4fff-8fff-ffffffffffff');
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": {
"item": {
"milestone_id": "fffffff4-ffff-4fff-8fff-ffffffffffff",
"deal_id": "eeeeeee4-eeee-4eee-8eee-eeeeeeeeeeee",
"deal_reference": "DEAL-2026-0009",
"deal_title": "Packaging run Q4",
"milestone_title": "Tooling and proof approval",
"status": "evidence_submitted",
"amount": 30000000,
"currency": "IDR",
"counterparty_name": "Harbourline Components Pte Ltd",
"risk_tier": "standard",
"evidence_summary": "Proof sheet signed by both sides on 10 October.",
"evidence_submitted_at": "2026-10-13T14:33:16.429Z",
"due_at": null,
"approvals_required": 1,
"approvals_done": 0,
"my_decision": null,
"submitted_by": {
"id": "88888884-8888-4888-8888-888888888888",
"name": "Sari Wibowo"
}
},
"proposed": {
"amount": 30000000,
"currency": "IDR",
"destination": {
"label": "Harbourline IDR collection account",
"bank_name": "DBS Bank Indonesia",
"account_number_masked": "3320****43"
},
"deal_progress_pct": 0
},
"how_we_got_here": [
{
"seq": 140528,
"event": "deal.created",
"entity_type": "deal",
"entity_id": "eeeeeee4-eeee-4eee-8eee-eeeeeeeeeeee",
"actor": {
"id": "33333333-3333-4333-8333-333333333333",
"name": "Dimas Hidayat"
},
"amount": 50000000,
"summary": "Created deal DEAL-2026-0009: Packaging run Q4",
"created_at": "2026-10-13T14:33:16.361Z"
},
{
"seq": 140529,
"event": "deal.updated",
"entity_type": "deal",
"entity_id": "eeeeeee4-eeee-4eee-8eee-eeeeeeeeeeee",
"actor": {
"id": "33333333-3333-4333-8333-333333333333",
"name": "Dimas Hidayat"
},
"amount": null,
"summary": "Updated deal DEAL-2026-0009",
"created_at": "2026-10-13T14:33:16.369Z"
}
],
"impact": {
"counterparty_paid": 30000000,
"money_moved": 30000000,
"milestones_remaining": 2,
"deal_progress_after_pct": 50
},
"decision": {
"risk_tier": "standard",
"approvals_required": 1,
"approvals": [
{
"id": "eeeeeee5-eeee-4eee-8eee-eeeeeeeeeeee",
"approver": null,
"status": "pending",
"note": null,
"decided_at": null
}
],
"reversibility": "A decision is final once saved. Release is a separate finance step.",
"expires_at": null
},
"evidence": [
{
"id": "88888885-8888-4888-8888-888888888888",
"title": "Signed proof sheet",
"document_type": "inspection",
"file_url": "https://files.example.com/northline/proof-sheet-q4.pdf",
"checksum_sha256": "a3f1c2d4e5b60718293a4b5c6d7e8f90112233445566778899aabbccddeeff00",
"visibility": "account",
"uploaded_by": "33333333-3333-4333-8333-333333333333",
"created_at": "2026-10-13T14:33:16.424Z"
}
]
}
}Lists are cut to the first 2 items and long text is shortened here.
Errors
| Status | Code | Cause |
|---|---|---|
404 | NOT_FOUND | No such milestone in the workspace |
401 | UNAUTHORIZED, SESSION_EXPIRED, INVALID_TOKEN | No session or key, the session expired, or the key is wrong or revoked |
400 | INVALID_ACCOUNT | X-Enterprise-Account is not a UUID |
403 | NO_WORKSPACE | You are not an active member of that workspace, or the key belongs to another one |
429 | RATE_LIMITED | Over the rate limit. Wait for Retry-After seconds |
Decide on evidence
Approves the evidence, rejects it or asks for a revision. A decision is final once saved; release is a separate step. With four-eyes the first approval leaves the milestone in evidence_submitted with approvals_done: 1, and the second makes it approved. Rejecting or asking for a revision closes the round and moves the milestone to revision_requested.
- Auth: A workspace API key (
Authorization: Bearer rpe_...), a personal API key or a dashboard session. - Role:
finance,adminorowner. - Rate limit: 30 a minute per user for this route, plus the shared 300 a minute.
- Idempotency: Send an
Idempotency-Keyheader (UUID v4). A retry with the same key and body returns the first answer withIdempotent-Replay: true.
Path parameters
| Name | Type | Description |
|---|---|---|
milestoneId | UUID | The milestone ID |
Body (JSON)
| Name | Type | Required | Description |
|---|---|---|---|
decision | string | Yes | approved, rejected or revision_requested |
note | string | No | At most 700 characters. Required, at least 5 characters, unless the decision is approved |
Example request
curl -X POST https://api.rekberpay.com/v1/enterprise/approvals/fffffff4-ffff-4fff-8fff-ffffffffffff/decide \
-H "Authorization: Bearer $REKBERPAY_WORKSPACE_KEY" \
-H "Idempotency-Key: fffffff5-ffff-4fff-8fff-ffffffffffff" \
-H "Content-Type: application/json" \
-d '{
"decision": "approved",
"note": "Proof matches the spec sheet."
}'const res = await fetch('https://api.rekberpay.com/v1/enterprise/approvals/fffffff4-ffff-4fff-8fff-ffffffffffff/decide', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.REKBERPAY_WORKSPACE_KEY}`,
'Content-Type': 'application/json',
'Idempotency-Key': 'fffffff5-ffff-4fff-8fff-ffffffffffff',
},
body: JSON.stringify({
decision: "approved",
note: "Proof matches the spec sheet."
}),
});
const data = await res.json();import os
import requests
resp = requests.post(
"https://api.rekberpay.com/v1/enterprise/approvals/fffffff4-ffff-4fff-8fff-ffffffffffff/decide",
headers={
"Authorization": f"Bearer {os.environ['REKBERPAY_WORKSPACE_KEY']}",
"Idempotency-Key": "fffffff5-ffff-4fff-8fff-ffffffffffff",
},
json={
"decision": "approved",
"note": "Proof matches the spec sheet.",
},
timeout=30,
)
print(resp.status_code, resp.json())<?php
$ch = curl_init('https://api.rekberpay.com/v1/enterprise/approvals/fffffff4-ffff-4fff-8fff-ffffffffffff/decide');
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('REKBERPAY_WORKSPACE_KEY'),
'Content-Type: application/json',
'Idempotency-Key: fffffff5-ffff-4fff-8fff-ffffffffffff',
],
CURLOPT_POSTFIELDS => json_encode([
'decision' => 'approved',
'note' => 'Proof matches the spec sheet.',
]),
]);
$response = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
curl_close($ch);Example response
200 OK
{
"success": true,
"data": {
"milestone_id": "fffffff4-ffff-4fff-8fff-ffffffffffff",
"approval_id": "eeeeeee5-eeee-4eee-8eee-eeeeeeeeeeee",
"decision": "approved",
"milestone_status": "approved",
"approvals_required": 1,
"approvals_done": 1
}
}Errors
| Status | Code | Cause |
|---|---|---|
400 | INVALID_BODY | Decision not valid, or a note is missing for a rejection or revision |
404 | NOT_FOUND | No such milestone |
409 | NOT_AWAITING_APPROVAL | The milestone is not evidence_submitted |
409 | ALREADY_DECIDED | You already decided in this round |
409 | SUBMITTER_CANNOT_APPROVE | You submitted the evidence, so someone else must decide |
409 | NO_PENDING_APPROVAL | Every approval in this round is taken |
409 | CONCURRENT_MODIFICATION | Someone changed the milestone first. Read it again |
401 | UNAUTHORIZED, SESSION_EXPIRED, INVALID_TOKEN | No session or key, the session expired, or the key is wrong or revoked |
400 | INVALID_ACCOUNT | X-Enterprise-Account is not a UUID |
403 | NO_WORKSPACE | You are not an active member of that workspace, or the key belongs to another one |
403 | FORBIDDEN | Your role is below finance |
403 | SCOPE_REQUIRED | The workspace key only has the read scope |
403 | CSRF_FAILED | Session call without the matching X-CSRF-Token header |
429 | RATE_LIMITED | Over the rate limit. Wait for Retry-After seconds |
A real SUBMITTER_CANNOT_APPROVE answer:
409 Conflict
{
"success": false,
"error": {
"code": "SUBMITTER_CANNOT_APPROVE",
"message": "The person who submitted the evidence cannot decide on it."
}
}Release a milestone
Releases an approved milestone to one to five verified payout instructions. The split amounts must add up to the milestone payout_amount exactly: the milestone amount minus its share of the platform fee the counterparty pays (equal to the amount when your workspace pays the whole fee, see Enterprise platform fee). Each split becomes a payout in queued; follow it with List payouts or the event feed. Release is final. A payout that fails at the bank stays failed with a failure_reason until RekberPay queues it again; the milestone stays released.
- Auth: A workspace API key (
Authorization: Bearer rpe_...), a personal API key or a dashboard session. - Role:
finance,adminorowner. - Rate limit: 30 a minute per user for this route, plus the shared 300 a minute.
- Idempotency: Send an
Idempotency-Keyheader (UUID v4). A retry with the same key and body returns the first answer withIdempotent-Replay: true.
Path parameters
| Name | Type | Description |
|---|---|---|
id | UUID | The deal ID |
mid | UUID | The milestone ID |
Body (JSON)
| Name | Type | Required | Description |
|---|---|---|---|
splits | array | Yes | 1 to 5 items of { payout_instruction_id, amount } |
splits[].payout_instruction_id | UUID | Yes | A verified instruction in the deal currency, for the deal counterparty or workspace-wide |
splits[].amount | integer | Yes | Whole rupiah, more than 0 |
Example request
curl -X POST https://api.rekberpay.com/v1/enterprise/deals/eeeeeee4-eeee-4eee-8eee-eeeeeeeeeeee/milestones/fffffff4-ffff-4fff-8fff-ffffffffffff/release \
-H "Authorization: Bearer $REKBERPAY_WORKSPACE_KEY" \
-H "Idempotency-Key: 33333336-3333-4333-8333-333333333333" \
-H "Content-Type: application/json" \
-d '{
"splits": [
{
"payout_instruction_id": "fffffff3-ffff-4fff-8fff-ffffffffffff",
"amount": 25000000
},
{
"payout_instruction_id": "22222224-2222-4222-8222-222222222222",
"amount": 5000000
}
]
}'const res = await fetch('https://api.rekberpay.com/v1/enterprise/deals/eeeeeee4-eeee-4eee-8eee-eeeeeeeeeeee/milestones/fffffff4-ffff-4fff-8fff-ffffffffffff/release', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.REKBERPAY_WORKSPACE_KEY}`,
'Content-Type': 'application/json',
'Idempotency-Key': '33333336-3333-4333-8333-333333333333',
},
body: JSON.stringify({
splits: [
{
payout_instruction_id: "fffffff3-ffff-4fff-8fff-ffffffffffff",
amount: 25000000
},
{
payout_instruction_id: "22222224-2222-4222-8222-222222222222",
amount: 5000000
}
]
}),
});
const data = await res.json();import os
import requests
resp = requests.post(
"https://api.rekberpay.com/v1/enterprise/deals/eeeeeee4-eeee-4eee-8eee-eeeeeeeeeeee/milestones/fffffff4-ffff-4fff-8fff-ffffffffffff/release",
headers={
"Authorization": f"Bearer {os.environ['REKBERPAY_WORKSPACE_KEY']}",
"Idempotency-Key": "33333336-3333-4333-8333-333333333333",
},
json={
"splits": [
{
"payout_instruction_id": "fffffff3-ffff-4fff-8fff-ffffffffffff",
"amount": 25000000,
},
{
"payout_instruction_id": "22222224-2222-4222-8222-222222222222",
"amount": 5000000,
},
],
},
timeout=30,
)
print(resp.status_code, resp.json())<?php
$ch = curl_init('https://api.rekberpay.com/v1/enterprise/deals/eeeeeee4-eeee-4eee-8eee-eeeeeeeeeeee/milestones/fffffff4-ffff-4fff-8fff-ffffffffffff/release');
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('REKBERPAY_WORKSPACE_KEY'),
'Content-Type: application/json',
'Idempotency-Key: 33333336-3333-4333-8333-333333333333',
],
CURLOPT_POSTFIELDS => json_encode([
'splits' => [
[
'payout_instruction_id' => 'fffffff3-ffff-4fff-8fff-ffffffffffff',
'amount' => 25000000,
],
[
'payout_instruction_id' => '22222224-2222-4222-8222-222222222222',
'amount' => 5000000,
],
],
]),
]);
$response = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
curl_close($ch);Example response
200 OK
{
"success": true,
"data": {
"milestone_id": "fffffff4-ffff-4fff-8fff-ffffffffffff",
"status": "released",
"deal_status": "in_progress",
"disbursements": [
{
"id": "44444446-4444-4444-8444-444444444444",
"payout_instruction_id": "fffffff3-ffff-4fff-8fff-ffffffffffff",
"amount": 25000000,
"currency": "IDR",
"status": "queued"
},
{
"id": "55555556-5555-4555-8555-555555555555",
"payout_instruction_id": "22222224-2222-4222-8222-222222222222",
"amount": 5000000,
"currency": "IDR",
"status": "queued"
}
]
}
}Errors
| Status | Code | Cause |
|---|---|---|
400 | INVALID_BODY | Splits missing, empty or more than 5 |
400 | SPLITS_MISMATCH | The splits do not add up to the milestone payout_amount (the amount minus the counterparty fee share) |
400 | INSTRUCTION_NOT_ELIGIBLE | An instruction is not verified, not in the deal currency or for another counterparty |
404 | NOT_FOUND | No such deal or milestone |
409 | NOT_APPROVED | Only an approved milestone can be released |
409 | TEST_DEAL | The deal was made in the sandbox with test money and the workspace is now live. It never releases real payouts |
409 | LIVE_DEAL_IN_SANDBOX | A live deal cannot release while the workspace is in the sandbox, and a live milestone funded with sandbox test money never releases real payouts |
409 | PAYOUT_INSTRUCTION_TAMPERED | An instruction failed its signature check. Contact RekberPay |
409 | CONCURRENT_MODIFICATION | The milestone changed while the call ran |
503 | SIGNING_NOT_CONFIGURED | Payout signing is not set up on the server |
401 | UNAUTHORIZED, SESSION_EXPIRED, INVALID_TOKEN | No session or key, the session expired, or the key is wrong or revoked |
400 | INVALID_ACCOUNT | X-Enterprise-Account is not a UUID |
403 | NO_WORKSPACE | You are not an active member of that workspace, or the key belongs to another one |
403 | FORBIDDEN | Your role is below finance |
403 | SCOPE_REQUIRED | The workspace key only has the read scope |
403 | CSRF_FAILED | Session call without the matching X-CSRF-Token header |
429 | RATE_LIMITED | Over the rate limit. Wait for Retry-After seconds |
A real SPLITS_MISMATCH answer:
400 Bad Request
{
"success": false,
"error": {
"code": "SPLITS_MISMATCH",
"message": "Splits must add up to the milestone amount."
}
}List payouts
Payouts (disbursements) newest first, with the deal, milestone and masked destination, plus a summary of the queue.
- Auth: A workspace API key (
Authorization: Bearer rpe_...), a personal API key or a dashboard session. - Role: Any workspace member (
viewerand up). - 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 | queued, processing, settled or failed |
page | integer | No | Default 1 |
limit | integer | No | 1 to 50. Default 20 |
Example request
curl "https://api.rekberpay.com/v1/enterprise/payouts?limit=2" \
-H "Authorization: Bearer $REKBERPAY_WORKSPACE_KEY"const res = await fetch('https://api.rekberpay.com/v1/enterprise/payouts?limit=2', {
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/payouts?limit=2",
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/payouts?limit=2');
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": {
"items": [
{
"id": "44444446-4444-4444-8444-444444444444",
"deal_id": "eeeeeee4-eeee-4eee-8eee-eeeeeeeeeeee",
"deal_reference": "DEAL-2026-0009",
"milestone_id": "fffffff4-ffff-4fff-8fff-ffffffffffff",
"milestone_title": "Tooling and proof approval",
"payout_instruction_id": "fffffff3-ffff-4fff-8fff-ffffffffffff",
"instruction_label": "Harbourline IDR collection account",
"bank_name": "DBS Bank Indonesia",
"account_number_masked": "3320****43",
"amount": 25000000,
"currency": "IDR",
"status": "queued",
"failure_reason": null,
"settled_at": null,
"created_at": "2026-10-13T14:33:16.480Z",
"updated_at": "2026-10-13T14:33:16.480Z"
},
{
"id": "55555556-5555-4555-8555-555555555555",
"deal_id": "eeeeeee4-eeee-4eee-8eee-eeeeeeeeeeee",
"deal_reference": "DEAL-2026-0009",
"milestone_id": "fffffff4-ffff-4fff-8fff-ffffffffffff",
"milestone_title": "Tooling and proof approval",
"payout_instruction_id": "22222224-2222-4222-8222-222222222222",
"instruction_label": "Harbourline freight and duties",
"bank_name": "Bank Central Asia",
"account_number_masked": "8730****55",
"amount": 5000000,
"currency": "IDR",
"status": "queued",
"failure_reason": null,
"settled_at": null,
"created_at": "2026-10-13T14:33:16.480Z",
"updated_at": "2026-10-13T14:33:16.480Z"
}
],
"summary": {
"today_count": 4,
"today_amount": 133000000,
"avg_settlement_minutes": 32,
"queued_amount": 58000000
}
},
"pagination": {
"page": 1,
"limit": 2,
"total": 8,
"total_pages": 4
}
}Errors
| Status | Code | Cause |
|---|---|---|
401 | UNAUTHORIZED, SESSION_EXPIRED, INVALID_TOKEN | No session or key, the session expired, or the key is wrong or revoked |
400 | INVALID_ACCOUNT | X-Enterprise-Account is not a UUID |
403 | NO_WORKSPACE | You are not an active member of that workspace, or the key belongs to another one |
429 | RATE_LIMITED | Over the rate limit. Wait for Retry-After seconds |
List payout instructions
Bank destinations, newest first. The full account number and the signature are never returned.
- Auth: A workspace API key (
Authorization: Bearer rpe_...), a personal API key or a dashboard session. - Role: Any workspace member (
viewerand up). - 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 |
|---|---|---|---|
counterparty_id | UUID | No | Only this counterparty |
status | string | No | draft, pending_review, verified or suspended |
page | integer | No | Default 1 |
limit | integer | No | 1 to 50. Default 20 |
Example request
curl "https://api.rekberpay.com/v1/enterprise/payout-instructions?counterparty_id=eeeeeee3-eeee-4eee-8eee-eeeeeeeeeeee" \
-H "Authorization: Bearer $REKBERPAY_WORKSPACE_KEY"const res = await fetch('https://api.rekberpay.com/v1/enterprise/payout-instructions?counterparty_id=eeeeeee3-eeee-4eee-8eee-eeeeeeeeeeee', {
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/payout-instructions?counterparty_id=eeeeeee3-eeee-4eee-8eee-eeeeeeeeeeee",
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/payout-instructions?counterparty_id=eeeeeee3-eeee-4eee-8eee-eeeeeeeeeeee');
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": "fffffff3-ffff-4fff-8fff-ffffffffffff",
"label": "Harbourline IDR collection account",
"counterparty": {
"id": "eeeeeee3-eeee-4eee-8eee-eeeeeeeeeeee",
"name": "Harbourline Components Pte Ltd"
},
"currency": "IDR",
"bank_name": "DBS Bank Indonesia",
"account_number_masked": "3320****43",
"account_holder": "Harbourline Components Pte Ltd",
"country_code": "ID",
"status": "verified",
"signed": true,
"verified_at": "2026-09-30T07:33:06.162Z",
"created_at": "2026-09-30T03:33:06.162Z",
"updated_at": "2026-10-13T14:33:06.231Z"
}
],
"pagination": {
"page": 1,
"limit": 20,
"total": 1,
"total_pages": 1
}
}Errors
| Status | Code | Cause |
|---|---|---|
401 | UNAUTHORIZED, SESSION_EXPIRED, INVALID_TOKEN | No session or key, the session expired, or the key is wrong or revoked |
400 | INVALID_ACCOUNT | X-Enterprise-Account is not a UUID |
403 | NO_WORKSPACE | You are not an active member of that workspace, or the key belongs to another one |
429 | RATE_LIMITED | Over the rate limit. Wait for Retry-After seconds |
Get a payout instruction
- Auth: A workspace API key (
Authorization: Bearer rpe_...), a personal API key or a dashboard session. - Role: Any workspace member (
viewerand up). - 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 payout instruction ID |
Example request
curl https://api.rekberpay.com/v1/enterprise/payout-instructions/fffffff3-ffff-4fff-8fff-ffffffffffff \
-H "Authorization: Bearer $REKBERPAY_WORKSPACE_KEY"const res = await fetch('https://api.rekberpay.com/v1/enterprise/payout-instructions/fffffff3-ffff-4fff-8fff-ffffffffffff', {
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/payout-instructions/fffffff3-ffff-4fff-8fff-ffffffffffff",
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/payout-instructions/fffffff3-ffff-4fff-8fff-ffffffffffff');
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": "fffffff3-ffff-4fff-8fff-ffffffffffff",
"label": "Harbourline IDR collection account",
"counterparty": {
"id": "eeeeeee3-eeee-4eee-8eee-eeeeeeeeeeee",
"name": "Harbourline Components Pte Ltd"
},
"currency": "IDR",
"bank_name": "DBS Bank Indonesia",
"account_number_masked": "3320****43",
"account_holder": "Harbourline Components Pte Ltd",
"country_code": "ID",
"status": "verified",
"signed": true,
"verified_at": "2026-09-30T07:33:06.162Z",
"created_at": "2026-09-30T03:33:06.162Z",
"updated_at": "2026-10-13T14:33:06.231Z"
}
}Errors
| Status | Code | Cause |
|---|---|---|
404 | NOT_FOUND | No such instruction in the workspace |
401 | UNAUTHORIZED, SESSION_EXPIRED, INVALID_TOKEN | No session or key, the session expired, or the key is wrong or revoked |
400 | INVALID_ACCOUNT | X-Enterprise-Account is not a UUID |
403 | NO_WORKSPACE | You are not an active member of that workspace, or the key belongs to another one |
429 | RATE_LIMITED | Over the rate limit. Wait for Retry-After seconds |
Add a payout instruction
Adds a bank destination. The server signs it and it starts as pending_review; RekberPay verifies it before it can receive money. In the sandbox it starts as a verified test instruction that only ever receives test payouts, and it is suspended when the workspace goes live. Spaces and dashes in account_number are dropped. The answer already masks the number.
- Auth: A workspace API key (
Authorization: Bearer rpe_...), a personal API key or a dashboard session. - Role:
finance,adminorowner. - Rate limit: 30 a minute per user for this route, plus the shared 300 a minute.
- Idempotency: Send an
Idempotency-Keyheader (UUID v4). A retry with the same key and body returns the first answer withIdempotent-Replay: true.
Body (JSON)
| Name | Type | Required | Description |
|---|---|---|---|
label | string | Yes | 2 to 120 characters |
currency | string | Yes | Three letters, for example IDR |
bank_name | string | Yes | 2 to 100 characters |
account_number | string | Yes | 6 to 80 letters or digits after spaces and dashes are removed |
account_holder | string | Yes | 2 to 160 characters |
country_code | string | Yes | Two letters |
counterparty_id | UUID | null | No | Tie it to one counterparty. Leave it out for a workspace-wide destination |
Example request
curl -X POST https://api.rekberpay.com/v1/enterprise/payout-instructions \
-H "Authorization: Bearer $REKBERPAY_WORKSPACE_KEY" \
-H "Idempotency-Key: 11111114-1111-4111-8111-111111111111" \
-H "Content-Type: application/json" \
-d '{
"label": "Harbourline freight account",
"counterparty_id": "eeeeeee3-eeee-4eee-8eee-eeeeeeeeeeee",
"currency": "IDR",
"bank_name": "Bank Central Asia",
"account_number": "8730 0412 55",
"account_holder": "PT Harbourline Indonesia",
"country_code": "ID"
}'const res = await fetch('https://api.rekberpay.com/v1/enterprise/payout-instructions', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.REKBERPAY_WORKSPACE_KEY}`,
'Content-Type': 'application/json',
'Idempotency-Key': '11111114-1111-4111-8111-111111111111',
},
body: JSON.stringify({
label: "Harbourline freight account",
counterparty_id: "eeeeeee3-eeee-4eee-8eee-eeeeeeeeeeee",
currency: "IDR",
bank_name: "Bank Central Asia",
account_number: "8730 0412 55",
account_holder: "PT Harbourline Indonesia",
country_code: "ID"
}),
});
const data = await res.json();import os
import requests
resp = requests.post(
"https://api.rekberpay.com/v1/enterprise/payout-instructions",
headers={
"Authorization": f"Bearer {os.environ['REKBERPAY_WORKSPACE_KEY']}",
"Idempotency-Key": "11111114-1111-4111-8111-111111111111",
},
json={
"label": "Harbourline freight account",
"counterparty_id": "eeeeeee3-eeee-4eee-8eee-eeeeeeeeeeee",
"currency": "IDR",
"bank_name": "Bank Central Asia",
"account_number": "8730 0412 55",
"account_holder": "PT Harbourline Indonesia",
"country_code": "ID",
},
timeout=30,
)
print(resp.status_code, resp.json())<?php
$ch = curl_init('https://api.rekberpay.com/v1/enterprise/payout-instructions');
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('REKBERPAY_WORKSPACE_KEY'),
'Content-Type: application/json',
'Idempotency-Key: 11111114-1111-4111-8111-111111111111',
],
CURLOPT_POSTFIELDS => json_encode([
'label' => 'Harbourline freight account',
'counterparty_id' => 'eeeeeee3-eeee-4eee-8eee-eeeeeeeeeeee',
'currency' => 'IDR',
'bank_name' => 'Bank Central Asia',
'account_number' => '8730 0412 55',
'account_holder' => 'PT Harbourline Indonesia',
'country_code' => 'ID',
]),
]);
$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": "22222224-2222-4222-8222-222222222222",
"label": "Harbourline freight account",
"counterparty": {
"id": "eeeeeee3-eeee-4eee-8eee-eeeeeeeeeeee",
"name": "Harbourline Components Pte Ltd"
},
"currency": "IDR",
"bank_name": "Bank Central Asia",
"account_number_masked": "8730****55",
"account_holder": "PT Harbourline Indonesia",
"country_code": "ID",
"status": "pending_review",
"signed": true,
"verified_at": null,
"created_at": "2026-10-13T14:33:16.301Z",
"updated_at": "2026-10-13T14:33:16.301Z"
}
}Errors
| Status | Code | Cause |
|---|---|---|
400 | INVALID_BODY | A field failed validation |
404 | COUNTERPARTY_NOT_FOUND | The counterparty is not in this workspace |
503 | SIGNING_NOT_CONFIGURED | Payout signing is not set up on the server |
401 | UNAUTHORIZED, SESSION_EXPIRED, INVALID_TOKEN | No session or key, the session expired, or the key is wrong or revoked |
400 | INVALID_ACCOUNT | X-Enterprise-Account is not a UUID |
403 | NO_WORKSPACE | You are not an active member of that workspace, or the key belongs to another one |
403 | FORBIDDEN | Your role is below finance |
403 | SCOPE_REQUIRED | The workspace key only has the read scope |
403 | CSRF_FAILED | Session call without the matching X-CSRF-Token header |
429 | RATE_LIMITED | Over the rate limit. Wait for Retry-After seconds |
Update a payout instruction
Any of the create fields. Changing a bank field or the counterparty signs the instruction again and sends it back to pending_review. A label change keeps its status.
- Auth: A workspace API key (
Authorization: Bearer rpe_...), a personal API key or a dashboard session. - Role:
finance,adminorowner. - Rate limit: 30 a minute per user for this route, plus the shared 300 a minute.
- Idempotency: Send an
Idempotency-Keyheader (UUID v4). A retry with the same key and body returns the first answer withIdempotent-Replay: true.
Path parameters
| Name | Type | Description |
|---|---|---|
id | UUID | The payout instruction ID |
Example request
curl -X PATCH https://api.rekberpay.com/v1/enterprise/payout-instructions/22222224-2222-4222-8222-222222222222 \
-H "Authorization: Bearer $REKBERPAY_WORKSPACE_KEY" \
-H "Idempotency-Key: 33333334-3333-4333-8333-333333333333" \
-H "Content-Type: application/json" \
-d '{
"label": "Harbourline freight and duties"
}'const res = await fetch('https://api.rekberpay.com/v1/enterprise/payout-instructions/22222224-2222-4222-8222-222222222222', {
method: 'PATCH',
headers: {
Authorization: `Bearer ${process.env.REKBERPAY_WORKSPACE_KEY}`,
'Content-Type': 'application/json',
'Idempotency-Key': '33333334-3333-4333-8333-333333333333',
},
body: JSON.stringify({
label: "Harbourline freight and duties"
}),
});
const data = await res.json();import os
import requests
resp = requests.patch(
"https://api.rekberpay.com/v1/enterprise/payout-instructions/22222224-2222-4222-8222-222222222222",
headers={
"Authorization": f"Bearer {os.environ['REKBERPAY_WORKSPACE_KEY']}",
"Idempotency-Key": "33333334-3333-4333-8333-333333333333",
},
json={
"label": "Harbourline freight and duties",
},
timeout=30,
)
print(resp.status_code, resp.json())<?php
$ch = curl_init('https://api.rekberpay.com/v1/enterprise/payout-instructions/22222224-2222-4222-8222-222222222222');
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'PATCH',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('REKBERPAY_WORKSPACE_KEY'),
'Content-Type: application/json',
'Idempotency-Key: 33333334-3333-4333-8333-333333333333',
],
CURLOPT_POSTFIELDS => json_encode([
'label' => 'Harbourline freight and duties',
]),
]);
$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": "22222224-2222-4222-8222-222222222222",
"label": "Harbourline freight and duties",
"counterparty": {
"id": "eeeeeee3-eeee-4eee-8eee-eeeeeeeeeeee",
"name": "Harbourline Components Pte Ltd"
},
"currency": "IDR",
"bank_name": "Bank Central Asia",
"account_number_masked": "8730****55",
"account_holder": "PT Harbourline Indonesia",
"country_code": "ID",
"status": "pending_review",
"signed": true,
"verified_at": null,
"created_at": "2026-10-13T14:33:16.301Z",
"updated_at": "2026-10-13T14:33:16.309Z"
}
}Errors
| Status | Code | Cause |
|---|---|---|
400 | INVALID_BODY | A field failed validation, or nothing to update |
404 | NOT_FOUND, COUNTERPARTY_NOT_FOUND | No such instruction, or the counterparty is not in this workspace |
409 | SUSPENDED | The instruction is suspended. Contact RekberPay |
409 | IN_USE | A payout to it is queued or processing |
409 | PAYOUT_INSTRUCTION_TAMPERED | The stored details no longer match their signature |
503 | SIGNING_NOT_CONFIGURED | Payout signing is not set up on the server |
401 | UNAUTHORIZED, SESSION_EXPIRED, INVALID_TOKEN | No session or key, the session expired, or the key is wrong or revoked |
400 | INVALID_ACCOUNT | X-Enterprise-Account is not a UUID |
403 | NO_WORKSPACE | You are not an active member of that workspace, or the key belongs to another one |
403 | FORBIDDEN | Your role is below finance |
403 | SCOPE_REQUIRED | The workspace key only has the read scope |
403 | CSRF_FAILED | Session call without the matching X-CSRF-Token header |
429 | RATE_LIMITED | Over the rate limit. Wait for Retry-After seconds |
Delete a payout instruction
Deletes an instruction that never received a payout.
- Auth: A workspace API key (
Authorization: Bearer rpe_...), a personal API key or a dashboard session. - Role:
finance,adminorowner. - Rate limit: 30 a minute per user for this route, plus the shared 300 a minute.
- Idempotency: Send an
Idempotency-Keyheader (UUID v4). A retry with the same key and body returns the first answer withIdempotent-Replay: true.
Path parameters
| Name | Type | Description |
|---|---|---|
id | UUID | The payout instruction ID |
Example request
curl -X DELETE https://api.rekberpay.com/v1/enterprise/payout-instructions/44444444-4444-4444-8444-444444444444 \
-H "Authorization: Bearer $REKBERPAY_WORKSPACE_KEY" \
-H "Idempotency-Key: 55555554-5555-4555-8555-555555555555"const res = await fetch('https://api.rekberpay.com/v1/enterprise/payout-instructions/44444444-4444-4444-8444-444444444444', {
method: 'DELETE',
headers: {
Authorization: `Bearer ${process.env.REKBERPAY_WORKSPACE_KEY}`,
'Idempotency-Key': '55555554-5555-4555-8555-555555555555',
},
});
const data = await res.json();import os
import requests
resp = requests.delete(
"https://api.rekberpay.com/v1/enterprise/payout-instructions/44444444-4444-4444-8444-444444444444",
headers={
"Authorization": f"Bearer {os.environ['REKBERPAY_WORKSPACE_KEY']}",
"Idempotency-Key": "55555554-5555-4555-8555-555555555555",
},
timeout=30,
)
print(resp.status_code, resp.json())<?php
$ch = curl_init('https://api.rekberpay.com/v1/enterprise/payout-instructions/44444444-4444-4444-8444-444444444444');
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'DELETE',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('REKBERPAY_WORKSPACE_KEY'),
'Idempotency-Key: 55555554-5555-4555-8555-555555555555',
],
]);
$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": "44444444-4444-4444-8444-444444444444",
"deleted": true
}
}Errors
| Status | Code | Cause |
|---|---|---|
404 | NOT_FOUND | No such instruction |
409 | IN_USE | The instruction has payouts |
401 | UNAUTHORIZED, SESSION_EXPIRED, INVALID_TOKEN | No session or key, the session expired, or the key is wrong or revoked |
400 | INVALID_ACCOUNT | X-Enterprise-Account is not a UUID |
403 | NO_WORKSPACE | You are not an active member of that workspace, or the key belongs to another one |
403 | FORBIDDEN | Your role is below finance |
403 | SCOPE_REQUIRED | The workspace key only has the read scope |
403 | CSRF_FAILED | Session call without the matching X-CSRF-Token header |
429 | RATE_LIMITED | Over the rate limit. Wait for Retry-After seconds |