Enterprise events and audit
Every change in a workspace is written to an append-only event feed. Each event has a seq number that only goes up, so you can poll the feed from the last seq you processed (see Polling events), or receive the same events as webhooks. Auth, roles, the workspace header and the shared limits are explained in Enterprise overview.
Endpoints
| Method | Path | Endpoint | Auth |
|---|---|---|---|
GET | /v1/enterprise/overview | Get the overview | viewer |
GET | /v1/enterprise/events | List events | viewer |
GET | /v1/enterprise/audit/export.csv | Export the audit log | finance |
GET | /v1/enterprise/reconciliation | Check the books | viewer |
Get the overview
The dashboard home in one call: headline numbers, a 24-hour series, 7-day trends, 90-day outcomes, counterparty performance, the approvals waiting for you, live alerts and the 8 newest events. It is cached for 15 seconds per workspace and user; your own writes clear the cache at once.
- Auth: A workspace API key (
Authorization: Bearer rpe_...), a personal API key or a dashboard session. - Role: Any workspace member (
viewerand up). - Rate limit: 60 a minute per user for this route, plus the shared 300 a minute.
- Idempotency: Not needed. Reading has no side effects.
Example request
curl https://api.rekberpay.com/v1/enterprise/overview \
-H "Authorization: Bearer $REKBERPAY_WORKSPACE_KEY"const res = await fetch('https://api.rekberpay.com/v1/enterprise/overview', {
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/overview",
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/overview');
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": {
"greeting_name": "Dimas",
"generated_at": "2026-10-13T14:33:16.656Z",
"stats": {
"active_deal_value": 772500000,
"deals_active": 6,
"transactions_secured": 8,
"released_24h": 641300000,
"need_you": 1,
"median_approval_hours": 4,
"avg_settlement_minutes": 31.8
},
"series_24h": [
{
"hour": "2026-10-12T15:00:00.000Z",
"funded": 1,
"approved": 0,
"released": 0,
"disputed": 1
},
{
"hour": "2026-10-12T16:00:00.000Z",
"funded": 1,
"approved": 1,
"released": 1,
"disputed": 0
}
],
"trends_7d": {
"days": [
"2026-10-07",
"2026-10-08"
],
"automation_pct": [
100,
100
],
"revisions": [
0,
0
]
},
"outcomes_90d": {
"released": 8,
"open": 12,
"disputed": 2,
"cancelled": 1,
"revision_rate_pct": 13.3,
"automation_rate_pct": 86.7
},
"counterparty_performance": [
{
"id": "eeeeeee3-eeee-4eee-8eee-eeeeeeeeeeee",
"name": "Harbourline Components Pte Ltd",
"deals": 4,
"on_time_pct": 100,
"dispute_pct": 12.5,
"spark": [
0,
0
]
},
{
"id": "22222228-2222-4222-8222-222222222222",
"name": "PT Sinar Logistik Nusantara",
"deals": 2,
"on_time_pct": 100,
"dispute_pct": 0,
"spark": [
0,
0
]
}
],
"needs_you": [
{
"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"
}
}
],
"alerts": [
{
"kind": "overdue_milestone",
"severity": "medium",
"title": "Milestone overdue",
"detail": "DEAL-2026-0005 / PO signed and registered was due 2026-10-10.",
"deal_id": "55555558-5555-4555-8555-555555555555",
"milestone_id": "66666668-6666-4666-8666-666666666666"
},
{
"kind": "kyb_gap",
"severity": "medium",
"title": "Counterparty KYB incomplete",
"detail": "DEAL-2026-0005 / PO signed and registered: PT Mitra Retail Jaya is pending.",
"deal_id": "55555558-5555-4555-8555-555555555555",
"milestone_id": "66666668-6666-4666-8666-666666666666"
}
],
"recent": [
{
"seq": 140566,
"id": "ddddddd7-dddd-4ddd-8ddd-dddddddddddd",
"event": "disbursement.queued",
"entity_type": "disbursement",
"entity_id": "ccccccc7-cccc-4ccc-8ccc-cccccccccccc",
"deal_id": "44444447-4444-4444-8444-444444444444",
"deal_reference": "DEAL-2026-0011",
"actor": {
"id": "33333333-3333-4333-8333-333333333333",
"name": "Dimas Hidayat"
},
"actor_label": null,
"amount": 150000000,
"summary": "Payout queued for DEAL-2026-0011 / Moulds delivered and inspected",
"created_at": "2026-10-13T14:33:16.635Z",
"livemode": false
},
{
"seq": 140565,
"id": "eeeeeee7-eeee-4eee-8eee-eeeeeeeeeeee",
"event": "milestone.released",
"entity_type": "milestone",
"entity_id": "55555557-5555-4555-8555-555555555555",
"deal_id": "44444447-4444-4444-8444-444444444444",
"deal_reference": "DEAL-2026-0011",
"actor": {
"id": "33333333-3333-4333-8333-333333333333",
"name": "Dimas Hidayat"
},
"actor_label": null,
"amount": 150000000,
"summary": "Released DEAL-2026-0011 / Moulds delivered and inspected in 1 payout",
"created_at": "2026-10-13T14:33:16.634Z",
"livemode": false
}
]
}
}Lists are cut to the first 2 items and long text is shortened here.
| Field | Type | Description |
|---|---|---|
stats | object | active_deal_value, deals_active, transactions_secured, released_24h, need_you, median_approval_hours, avg_settlement_minutes |
series_24h | array | 24 hourly buckets of funded, approved, released and disputed counts |
trends_7d | object | Seven UTC days of automation_pct and revisions |
outcomes_90d | object | Counts and rates over 90 days |
needs_you | array | Milestones waiting for your decision |
alerts | array | dispute_sla, overdue_milestone, high_value_pending, payout_velocity, watchlist_hit and kyb_gap alerts |
recent | array | The 8 newest events |
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 events
Oldest first after after (the default), for polling. With order=desc it returns the newest events first below before, plus next_before for the next older page. Both forms return next_after: send it as after on your next poll. When nothing is new, next_after echoes your after. Event items never carry IP addresses. Each item has livemode, the same flag as the webhook payload: fixed when the event happens, true only for real money in a live workspace, false for sandbox and test data.
- 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 |
|---|---|---|---|
after | integer | No | Events with seq above this. Default 0 |
order | string | No | asc (default) or desc |
before | integer | No | With order=desc, events with seq below this |
limit | integer | No | 1 to 100. Default 50 |
deal_id | UUID | No | Only this deal |
event | string | No | Only this event name, for example milestone.released |
Example request
curl "https://api.rekberpay.com/v1/enterprise/events?after=140563&limit=3" \
-H "Authorization: Bearer $REKBERPAY_WORKSPACE_KEY"const res = await fetch('https://api.rekberpay.com/v1/enterprise/events?after=140563&limit=3', {
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/events?after=140563&limit=3",
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/events?after=140563&limit=3');
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": [
{
"seq": 140564,
"id": "fffffff7-ffff-4fff-8fff-ffffffffffff",
"event": "milestone.approved",
"entity_type": "milestone",
"entity_id": "55555557-5555-4555-8555-555555555555",
"deal_id": "44444447-4444-4444-8444-444444444444",
"deal_reference": "DEAL-2026-0011",
"actor": {
"id": "77777776-7777-4777-8777-777777777777",
"name": "Maya Santoso"
},
"actor_label": null,
"amount": 150000000,
"summary": "Approved DEAL-2026-0011 / Moulds delivered and inspected for release",
"created_at": "2026-10-13T14:33:16.626Z",
"livemode": false
},
{
"seq": 140565,
"id": "eeeeeee7-eeee-4eee-8eee-eeeeeeeeeeee",
"event": "milestone.released",
"entity_type": "milestone",
"entity_id": "55555557-5555-4555-8555-555555555555",
"deal_id": "44444447-4444-4444-8444-444444444444",
"deal_reference": "DEAL-2026-0011",
"actor": {
"id": "33333333-3333-4333-8333-333333333333",
"name": "Dimas Hidayat"
},
"actor_label": null,
"amount": 150000000,
"summary": "Released DEAL-2026-0011 / Moulds delivered and inspected in 1 payout",
"created_at": "2026-10-13T14:33:16.634Z",
"livemode": false
},
{
"seq": 140566,
"id": "ddddddd7-dddd-4ddd-8ddd-dddddddddddd",
"event": "disbursement.queued",
"entity_type": "disbursement",
"entity_id": "ccccccc7-cccc-4ccc-8ccc-cccccccccccc",
"deal_id": "44444447-4444-4444-8444-444444444444",
"deal_reference": "DEAL-2026-0011",
"actor": {
"id": "33333333-3333-4333-8333-333333333333",
"name": "Dimas Hidayat"
},
"actor_label": null,
"amount": 150000000,
"summary": "Payout queued for DEAL-2026-0011 / Moulds delivered and inspected",
"created_at": "2026-10-13T14:33:16.635Z",
"livemode": false
}
],
"next_after": 140566
}
}| Field | Type | Description |
|---|---|---|
seq | integer | Feed position. Store the highest one you processed |
event | string | Event name. The list is in Webhooks |
actor | object | null | The member who acted |
actor_label | string | null | Set when the actor is not a member, for example RekberPay ops |
amount | number | null | Rupiah moved by the event, when there is one |
Errors
| Status | Code | Cause |
|---|---|---|
400 | INVALID_QUERY | order is not asc or desc, deal_id is not a UUID, or event is not a valid name |
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 |
A real INVALID_QUERY answer:
400 Bad Request
{
"success": false,
"error": {
"code": "INVALID_QUERY",
"message": "order must be asc or desc."
}
}Export the audit log
The event log as CSV, for auditors. Columns: seq, created_at, event, actor, ip, deal, amount, summary, previous_state, new_state. The ip column is only there for admins and owners; a finance member gets the same file without it. One export holds at most 10,000 rows; when there were more, the answer has X-Export-Truncated: true and you should export a shorter range. Cells a spreadsheet would read as a formula start with '.
- Auth: A workspace API key (
Authorization: Bearer rpe_...), a personal API key or a dashboard session. - Role:
finance,adminorowner. - Rate limit: 5 a minute per user for this route, plus the shared 300 a minute.
- Idempotency: Not needed. Reading has no side effects.
Query parameters
| Name | Type | Required | Description |
|---|---|---|---|
from | string | No | First day, YYYY-MM-DD. Default 90 days ago |
to | string | No | Last day, YYYY-MM-DD. Default today |
Example request
curl "https://api.rekberpay.com/v1/enterprise/audit/export.csv?from=2026-10-13&to=2026-10-13" \
-H "Authorization: Bearer $REKBERPAY_WORKSPACE_KEY" \
-o export.csvconst res = await fetch('https://api.rekberpay.com/v1/enterprise/audit/export.csv?from=2026-10-13&to=2026-10-13', {
headers: {
Authorization: `Bearer ${process.env.REKBERPAY_WORKSPACE_KEY}`,
},
});
const csv = await res.text();import os
import requests
resp = requests.get(
"https://api.rekberpay.com/v1/enterprise/audit/export.csv?from=2026-10-13&to=2026-10-13",
headers={
"Authorization": f"Bearer {os.environ['REKBERPAY_WORKSPACE_KEY']}",
},
timeout=30,
)
open("export.csv", "wb").write(resp.content)<?php
$ch = curl_init('https://api.rekberpay.com/v1/enterprise/audit/export.csv?from=2026-10-13&to=2026-10-13');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('REKBERPAY_WORKSPACE_KEY'),
],
]);
file_put_contents('export.csv', curl_exec($ch));
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
curl_close($ch);Example response
200 OK with Content-Disposition: attachment; filename="rekberpay-enterprise-audit-fffffff2-ffff-4fff-8fff-ffffffffffff-2026-10-13.csv"
seq,created_at,event,actor,ip,deal,amount,summary,previous_state,new_state
140451,2026-10-13T01:16:03.709Z,milestone.funded,Sandbox API key,,,21000000.00,Sandbox run SBX-408: funded test milestone via sandbox,,"{""status"":""funded"",""funding_method"":""sandbox""}"
140452,2026-10-13T01:29:41.428Z,milestone.approved,Sandbox API key,,,21000000.00,Sandbox run SBX-408: approved test milestone for release,,"{""status"":""approved""}"
140453,2026-10-13T01:36:48.405Z,milestone.released,Sandbox API key,,,21000000.00,Sandbox run SBX-408: released test milestone in 1 payout,,"{""status"":""released""}"The header row and the first 3 rows of the file.
Errors
| Status | Code | Cause |
|---|---|---|
400 | INVALID_QUERY | A date is not YYYY-MM-DD, or from is after to |
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 |
429 | RATE_LIMITED | Over the rate limit. Wait for Retry-After seconds |
Check the books
Compares every deal's stored funded and released totals with the sum of its milestones. balanced_all is null when the workspace has more than 1,000 deals and only the newest 1,000 were checked (truncated: true).
- 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.
Example request
curl https://api.rekberpay.com/v1/enterprise/reconciliation \
-H "Authorization: Bearer $REKBERPAY_WORKSPACE_KEY"const res = await fetch('https://api.rekberpay.com/v1/enterprise/reconciliation', {
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/reconciliation",
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/reconciliation');
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": {
"deals": [
{
"id": "44444447-4444-4444-8444-444444444444",
"reference": "DEAL-2026-0011",
"stored_funded": 150000000,
"computed_funded": 150000000,
"stored_released": 150000000,
"computed_released": 150000000,
"balanced": true
},
{
"id": "fffffff6-ffff-4fff-8fff-ffffffffffff",
"reference": "DEAL-2026-0010",
"stored_funded": 0,
"computed_funded": 0,
"stored_released": 0,
"computed_released": 0,
"balanced": true
},
{
"id": "eeeeeee4-eeee-4eee-8eee-eeeeeeeeeeee",
"reference": "DEAL-2026-0009",
"stored_funded": 50000000,
"computed_funded": 50000000,
"stored_released": 30000000,
"computed_released": 30000000,
"balanced": true
}
],
"balanced_all": true,
"truncated": false,
"checked_at": "2026-10-13T14:33:16.674Z"
}
}Lists are cut to the first 3 items and long text is shortened here.
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 |