Skip to content

Enterprise quickstart ​

This guide walks one milestone through the whole enterprise flow: create a deal, activate it, fund it in the sandbox, submit evidence, collect two approvals, release to two bank accounts, follow the event feed and export the audit log. Every request was sent to a real API and every response below is the real answer. IDs are replaced with sample UUIDs and the names belong to the demo workspace.

You need a sandbox workspace. New workspaces start in the sandbox, and the sandbox is the only place where POST .../fund works. In a live workspace RekberPay confirms funding when the money arrives.

Before you start ​

Who calls what ​

The enterprise API acts as a signed-in user, and that user's workspace role decides what is allowed. The flow below needs three roles. Four-eyes control means the person who submits evidence can never approve it, and two different people must approve a large milestone.

StepRole neededKey used below
Create and activate the deal, add documents, submit evidenceoperator$OPERATOR_KEY
Fund, approve, releasefinance$FINANCE_KEY
Second approvaladmin or owner (any second approver with the finance role or higher)$ADMIN_KEY

Choose how to authenticate ​

There are three ways to call the enterprise API:

MethodHeaderWorkspace
Workspace API keyAuthorization: Bearer rpe_...Fixed by the key. No X-Enterprise-Account needed
Personal API keyAuthorization: Bearer <api-key>X-Enterprise-Account, or the workspace you joined first
Session cookieThe cookie from POST /v1/auth/login, plus X-CSRF-Token on writesX-Enterprise-Account, or the workspace you joined first

A workspace API key is the usual choice for a server integration. An admin or owner creates it on the Developers page of the dashboard (or with POST /v1/enterprise/api-keys). It starts with rpe_, is shown once, and only works under /v1/enterprise:

bash
curl https://api.rekberpay.com/v1/enterprise/deals \
  -H "Authorization: Bearer rpe_..."

A read key acts as a viewer. A read + write key acts with the role of the admin who created it, so one key is one person for four-eyes purposes: it cannot both submit evidence and approve it, or give both approvals. A key cannot change members, settings or webhooks and cannot call /me, invites, notifications or /api-keys. See Workspace API keys.

This guide uses personal API keys, one per role, because the flow needs three different people.

Authenticate with an API key ​

Each person creates their own key from their account with POST /v1/auth/me/api-key/rotate (see Authentication and API keys), then sends it on every call:

http
Authorization: Bearer <api-key>
X-Enterprise-Account: 11111111-1111-4111-8111-111111111111

A key acts as the user who created it, so create a dedicated user for each integration and give it the smallest role that works. In a browser the same calls work with the session cookie plus an X-CSRF-Token header on writes. Bearer calls do not need the CSRF header.

X-Enterprise-Account is the workspace id. If you leave it out, the API uses the workspace you joined first. GET /v1/enterprise/me shows the id.

Setup ​

bash
export BASE=https://api.rekberpay.com
export WORKSPACE_ID=11111111-1111-4111-8111-111111111111
export OPERATOR_KEY=...   # operator user's key
export FINANCE_KEY=...    # finance user's key
export ADMIN_KEY=...      # admin user's key

1. Check the workspace ​

bash
curl "$BASE/v1/enterprise/me" \
  -H "Authorization: Bearer $FINANCE_KEY" \
  -H "X-Enterprise-Account: $WORKSPACE_ID"
json
{
    "success": true,
    "data": {
        "account": {
            "id": "ccccccc4-cccc-4ccc-8ccc-cccccccccccc",
            "display_name": "Northline Trading",
            "legal_name": "PT Northline Trading Indonesia",
            "country_code": "ID",
            "kyb_status": "verified",
            "risk_tier": "standard",
            "is_sandbox": true,
            "settings": {
                "four_eyes_threshold": 100000000,
                "dispute_sla_hours": 24,
                "watchlist": [
                    "Blacklisted Supplier"
                ],
                "account_manager": {
                    "name": "Maya Patel",
                    "email": "[email protected]"
                }
            }
        },
        "membership": {
            "id": "ddddddd4-dddd-4ddd-8ddd-dddddddddddd",
            "role": "finance",
            "joined_at": "2026-09-29T04:01:15.108Z"
        },
        "memberships": [
            {
                "account_id": "ccccccc4-cccc-4ccc-8ccc-cccccccccccc",
                "display_name": "Northline Trading",
                "role": "finance"
            }
        ],
        "invites": []
    }
}

is_sandbox: true means funding works. four_eyes_threshold is the amount at which a milestone needs two approvals. Here it is IDR 100,000,000, so the IDR 150,000,000 milestone below needs two.

2. Pick the counterparty and its payout instructions ​

A deal needs a verified counterparty, and a release can only pay a verified payout instruction that belongs to that counterparty. Look them up:

bash
curl "$BASE/v1/enterprise/counterparties?q=Harbourline&kyb_status=verified" \
  -H "Authorization: Bearer $FINANCE_KEY" \
  -H "X-Enterprise-Account: $WORKSPACE_ID"
json
{
    "success": true,
    "data": [
        {
            "id": "eeeeeee6-eeee-4eee-8eee-eeeeeeeeeeee",
            "name": "Harbourline Components Pte Ltd",
            "country_code": "SG",
            "contact_email": "[email protected]",
            "contact_name": "Daniel Tan",
            "relationship": "seller",
            "kyb_status": "verified",
            "created_at": "2026-09-29T10:01:15.108Z"
        }
    ],
    "pagination": {
        "page": 1,
        "limit": 20,
        "total": 1,
        "total_pages": 1
    }
}

Use the id as counterparty_id in the next steps. To add a new counterparty, POST /v1/enterprise/counterparties as an operator. Its KYB status starts at not_started and is set by RekberPay, not by you.

bash
curl "$BASE/v1/enterprise/payout-instructions?counterparty_id=22222221-2222-4222-8222-222222222222&status=verified" \
  -H "Authorization: Bearer $FINANCE_KEY" \
  -H "X-Enterprise-Account: $WORKSPACE_ID"
json
{
    "success": true,
    "data": [
        {
            "id": "ddddddd6-dddd-4ddd-8ddd-dddddddddddd",
            "label": "Harbourline IDR collection account",
            "counterparty": {
                "id": "eeeeeee6-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-29T20:31:15.108Z",
            "created_at": "2026-09-29T16:31:15.108Z",
            "updated_at": "2026-10-13T03:31:15.210Z"
        }
    ],
    "pagination": {
        "page": 1,
        "limit": 20,
        "total": 1,
        "total_pages": 1
    }
}

Account numbers are never returned in full. A new instruction (POST /payout-instructions, finance role) starts as pending_review and cannot receive money until RekberPay operations verifies it. This walkthrough uses two verified instructions on the same counterparty, because step 8 splits the payout across both. If you only have one, send a single split.

3. Note where the event feed is ​

Read the newest event once, and keep its next_after as your cursor. Everything you do from here shows up after it.

bash
curl "$BASE/v1/enterprise/events?order=desc&limit=1" \
  -H "Authorization: Bearer $FINANCE_KEY" \
  -H "X-Enterprise-Account: $WORKSPACE_ID"
json
{
    "success": true,
    "data": {
        "items": [
            {
                "seq": 135668,
                "id": "00000007-0000-4000-8000-000000000000",
                "event": "milestone.approved",
                "entity_type": "milestone",
                "entity_id": "aaaaaaa7-aaaa-4aaa-8aaa-aaaaaaaaaaaa",
                "deal_id": "bbbbbbb7-bbbb-4bbb-8bbb-bbbbbbbbbbbb",
                "deal_reference": "DEAL-2026-0003",
                "actor": {
                    "id": "77777772-7777-4777-8777-777777777777",
                    "name": "Maya Santoso"
                },
                "actor_label": null,
                "amount": 150000000,
                "summary": "Approved DEAL-2026-0003 / Goods in transit for release",
                "created_at": "2026-10-13T03:22:12.962Z",
                "livemode": false
            }
        ],
        "next_before": 135668,
        "next_after": 135668
    }
}

4. Create the deal ​

Send an Idempotency-Key (a UUID v4) so a retry cannot create a second deal. A deal holds 1 to 20 milestones and starts as a draft.

bash
curl -X POST "$BASE/v1/enterprise/deals" \
  -H "Authorization: Bearer $OPERATOR_KEY" \
  -H "X-Enterprise-Account: $WORKSPACE_ID" \
  -H "Idempotency-Key: 66666665-6666-4666-8666-666666666666" \
  -H "Content-Type: application/json" \
  -d '{
    "title": "Electronic control boards, lot 8",
    "external_ref": "PO-2026-0188",
    "counterparty_id": "22222221-2222-4222-8222-222222222222",
    "currency": "IDR",
    "cross_border": true,
    "milestones": [
      { "title": "PO signed and registered", "amount": 50000000,
        "required_evidence": "Countersigned PO", "due_at": "2026-10-20T10:00:00+07:00" },
      { "title": "Goods in transit", "amount": 150000000,
        "required_evidence": "Bill of lading and export declaration" },
      { "title": "Final delivery", "amount": 60000000 }
    ]
  }'
json
{
    "success": true,
    "data": {
        "deal": {
            "id": "88888885-8888-4888-8888-888888888888",
            "reference": "DEAL-2026-0009",
            "title": "Electronic control boards, lot 8",
            "description": null,
            "external_ref": "PO-2026-0188",
            "status": "draft",
            "currency": "IDR",
            "total_amount": 260000000,
            "funded_amount": 0,
            "released_amount": 0,
            "cross_border": true,
            "owner": {
                "id": "99999995-9999-4999-8999-999999999999",
                "name": "Sari Wibowo"
            },
            "created_at": "2026-10-13T03:23:04.417Z",
            "updated_at": "2026-10-13T03:23:04.417Z",
            "counterparty": {
                "id": "00000005-0000-4000-8000-000000000000",
                "name": "Harbourline Components Pte Ltd",
                "country_code": "SG",
                "kyb_status": "verified",
                "relationship": "seller"
            }
        },
        "milestones": [
            {
                "id": "aaaaaaa5-aaaa-4aaa-8aaa-aaaaaaaaaaaa",
                "sequence_no": 1,
                "title": "PO signed and registered",
                "required_evidence": "Countersigned PO",
                "amount": 50000000,
                "status": "draft",
                "due_at": "2026-10-20T03:00:00.000Z",
                "funded_at": null,
                "funding_method": null,
                "evidence_summary": null,
                "evidence_submitted_at": null,
                "approved_at": null,
                "released_at": null,
                "disputed_at": null,
                "dispute_reason": null,
                "approvals_required": 1,
                "approvals": [],
                "documents": [],
                "disbursements": []
            },
            {
                "id": "bbbbbbb5-bbbb-4bbb-8bbb-bbbbbbbbbbbb",
                "sequence_no": 2,
                "title": "Goods in transit",
                "required_evidence": "Bill of lading and export declaration",
                "amount": 150000000,
                "status": "draft",
                "due_at": null,
                "funded_at": null,
                "funding_method": null,
                "evidence_summary": null,
                "evidence_submitted_at": null,
                "approved_at": null,
                "released_at": null,
                "disputed_at": null,
                "dispute_reason": null,
                "approvals_required": 2,
                "approvals": [],
                "documents": [],
                "disbursements": []
            },
            {
                "id": "ccccccc5-cccc-4ccc-8ccc-cccccccccccc",
                "sequence_no": 3,
                "title": "Final delivery",
                "required_evidence": null,
                "amount": 60000000,
                "status": "draft",
                "due_at": null,
                "funded_at": null,
                "funding_method": null,
                "evidence_summary": null,
                "evidence_submitted_at": null,
                "approved_at": null,
                "released_at": null,
                "disputed_at": null,
                "dispute_reason": null,
                "approvals_required": 1,
                "approvals": [],
                "documents": [],
                "disbursements": []
            }
        ],
        "events": [
            {
                "seq": 135673,
                "event": "deal.created",
                "entity_type": "deal",
                "entity_id": "88888885-8888-4888-8888-888888888888",
                "actor": {
                    "id": "99999995-9999-4999-8999-999999999999",
                    "name": "Sari Wibowo"
                },
                "amount": 260000000,
                "summary": "Created deal DEAL-2026-0009: Electronic control boards, lot 8",
                "created_at": "2026-10-13T03:23:04.418Z"
            }
        ],
        "outcome": {
            "time_to_release_minutes": null,
            "approvals_by_people": 0,
            "revisions": 0
        }
    }
}

The deal's total_amount, funded_amount, released_amount and status are computed from the milestones. Note approvals_required: 2 for the IDR 150,000,000 milestone, 1 for the other two.

5. Activate it ​

bash
curl -X POST "$BASE/v1/enterprise/deals/33333331-3333-4333-8333-333333333333/activate" \
  -H "Authorization: Bearer $OPERATOR_KEY" \
  -H "X-Enterprise-Account: $WORKSPACE_ID" \
  -H "Idempotency-Key: ccccccc7-cccc-4ccc-8ccc-cccccccccccc" \
  -H "Content-Type: application/json" \
  -d '{}'

The answer is the full deal again, now awaiting_funding. The first response is cut to the deal summary here:

json
{
    "success": true,
    "data": {
        "deal": {
            "id": "88888885-8888-4888-8888-888888888888",
            "reference": "DEAL-2026-0009",
            "title": "Electronic control boards, lot 8",
            "description": null,
            "external_ref": "PO-2026-0188",
            "status": "awaiting_funding",
            "currency": "IDR",
            "total_amount": 260000000,
            "funded_amount": 0,
            "released_amount": 0,
            "cross_border": true,
            "owner": {
                "id": "99999995-9999-4999-8999-999999999999",
                "name": "Sari Wibowo"
            },
            "created_at": "2026-10-13T03:23:04.417Z",
            "updated_at": "2026-10-13T03:23:04.509Z",
            "counterparty": {
                "id": "00000005-0000-4000-8000-000000000000",
                "name": "Harbourline Components Pte Ltd",
                "country_code": "SG",
                "kyb_status": "verified",
                "relationship": "seller"
            }
        }
    }
}

6. Fund the milestone (sandbox) ​

bash
curl -X POST "$BASE/v1/enterprise/deals/33333331-3333-4333-8333-333333333333/milestones/55555551-5555-4555-8555-555555555555/fund" \
  -H "Authorization: Bearer $FINANCE_KEY" \
  -H "X-Enterprise-Account: $WORKSPACE_ID" \
  -H "Idempotency-Key: ddddddd7-dddd-4ddd-8ddd-dddddddddddd" \
  -H "Content-Type: application/json" \
  -d '{ "method": "sandbox", "reference": "TEST-001" }'
json
{
    "success": true,
    "data": {
        "milestone": {
            "id": "bbbbbbb5-bbbb-4bbb-8bbb-bbbbbbbbbbbb",
            "deal_id": "88888885-8888-4888-8888-888888888888",
            "status": "funded",
            "amount": 150000000,
            "funding_method": "sandbox"
        },
        "deal_status": "funded"
    }
}

7. Submit evidence ​

Register the file first. Documents are metadata: the file itself lives at an https URL.

bash
curl -X POST "$BASE/v1/enterprise/documents" \
  -H "Authorization: Bearer $OPERATOR_KEY" \
  -H "X-Enterprise-Account: $WORKSPACE_ID" \
  -H "Content-Type: application/json" \
  -d '{
    "title": "Bill of lading HLCU-5521",
    "document_type": "shipping",
    "file_url": "https://files.northline.example/bl/HLCU-5521.pdf",
    "checksum_sha256": "2d711642b726b04401627ca9fbac32f5c8530fb1903cc4db02258717921a4881",
    "deal_id": "33333331-3333-4333-8333-333333333333",
    "milestone_id": "55555551-5555-4555-8555-555555555555"
  }'
json
{
    "success": true,
    "data": {
        "id": "eeeeeee7-eeee-4eee-8eee-eeeeeeeeeeee",
        "deal_id": "88888885-8888-4888-8888-888888888888",
        "milestone_id": "bbbbbbb5-bbbb-4bbb-8bbb-bbbbbbbbbbbb",
        "document_type": "shipping",
        "title": "Bill of lading HLCU-5521",
        "file_url": "https://files.northline.example/bl/HLCU-5521.pdf",
        "checksum_sha256": "2d711642b726b04401627ca9fbac32f5c8530fb1903cc4db02258717921a4881",
        "visibility": "account",
        "uploaded_by": "99999995-9999-4999-8999-999999999999"
    }
}

Then submit it with a short summary:

bash
curl -X POST "$BASE/v1/enterprise/deals/33333331-3333-4333-8333-333333333333/milestones/55555551-5555-4555-8555-555555555555/submit-evidence" \
  -H "Authorization: Bearer $OPERATOR_KEY" \
  -H "X-Enterprise-Account: $WORKSPACE_ID" \
  -H "Idempotency-Key: ddddddd5-dddd-4ddd-8ddd-dddddddddddd" \
  -H "Content-Type: application/json" \
  -d '{
    "evidence_summary": "Bill of lading HLCU-5521 and the export declaration for all 600 boards.",
    "document_ids": ["99999991-9999-4999-8999-999999999999"]
  }'
json
{
    "success": true,
    "data": {
        "milestone_id": "bbbbbbb5-bbbb-4bbb-8bbb-bbbbbbbbbbbb",
        "status": "evidence_submitted",
        "approvals_required": 2,
        "deal_status": "funded"
    }
}

The milestone is now evidence_submitted and waiting for two approvals. deal_status is still funded because nothing has been released yet.

8. Decide: two approvers ​

A finance member can read everything needed to decide in one call:

bash
curl "$BASE/v1/enterprise/approvals/55555551-5555-4555-8555-555555555555" \
  -H "Authorization: Bearer $FINANCE_KEY" \
  -H "X-Enterprise-Account: $WORKSPACE_ID"

The answer has item, proposed (amount and the masked destination), how_we_got_here, impact, decision and evidence. It is cut to the first two how_we_got_here events here:

json
{
    "success": true,
    "data": {
        "item": {
            "milestone_id": "bbbbbbb5-bbbb-4bbb-8bbb-bbbbbbbbbbbb",
            "deal_id": "88888885-8888-4888-8888-888888888888",
            "deal_reference": "DEAL-2026-0009",
            "deal_title": "Electronic control boards, lot 8",
            "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-13T03:23:04.614Z",
            "due_at": null,
            "approvals_required": 2,
            "approvals_done": 0,
            "my_decision": null,
            "submitted_by": {
                "id": "99999995-9999-4999-8999-999999999999",
                "name": "Sari Wibowo"
            }
        },
        "proposed": {
            "amount": 150000000,
            "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": 135673,
                "event": "deal.created",
                "entity_type": "deal",
                "entity_id": "88888885-8888-4888-8888-888888888888",
                "actor": {
                    "id": "99999995-9999-4999-8999-999999999999",
                    "name": "Sari Wibowo"
                },
                "amount": 260000000,
                "summary": "Created deal DEAL-2026-0009: Electronic control boards, lot 8",
                "created_at": "2026-10-13T03:23:04.418Z"
            },
            {
                "seq": 135674,
                "event": "deal.activated",
                "entity_type": "deal",
                "entity_id": "88888885-8888-4888-8888-888888888888",
                "actor": {
                    "id": "99999995-9999-4999-8999-999999999999",
                    "name": "Sari Wibowo"
                },
                "amount": null,
                "summary": "Activated deal DEAL-2026-0009",
                "created_at": "2026-10-13T03:23:04.510Z"
            }
        ],
        "impact": {
            "counterparty_paid": 150000000,
            "money_moved": 150000000,
            "milestones_remaining": 2,
            "deal_progress_after_pct": 58
        },
        "decision": {
            "risk_tier": "standard",
            "approvals_required": 2,
            "approvals": [
                {
                    "id": "55555556-5555-4555-8555-555555555555",
                    "approver": null,
                    "status": "pending",
                    "note": null,
                    "decided_at": null
                },
                {
                    "id": "66666666-6666-4666-8666-666666666666",
                    "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": "eeeeeee7-eeee-4eee-8eee-eeeeeeeeeeee",
                "title": "Bill of lading HLCU-5521",
                "document_type": "shipping",
                "file_url": "https://files.northline.example/bl/HLCU-5521.pdf",
                "checksum_sha256": "2d711642b726b04401627ca9fbac32f5c8530fb1903cc4db02258717921a4881",
                "visibility": "account",
                "uploaded_by": "99999995-9999-4999-8999-999999999999",
                "created_at": "2026-10-13T03:23:04.563Z"
            }
        ]
    }
}

Now the first approver decides:

bash
curl -X POST "$BASE/v1/enterprise/approvals/55555551-5555-4555-8555-555555555555/decide" \
  -H "Authorization: Bearer $FINANCE_KEY" \
  -H "X-Enterprise-Account: $WORKSPACE_ID" \
  -H "Idempotency-Key: 44444446-4444-4444-8444-444444444444" \
  -H "Content-Type: application/json" \
  -d '{ "decision": "approved" }'
json
{
    "success": true,
    "data": {
        "milestone_id": "bbbbbbb5-bbbb-4bbb-8bbb-bbbbbbbbbbbb",
        "approval_id": "55555556-5555-4555-8555-555555555555",
        "decision": "approved",
        "milestone_status": "evidence_submitted",
        "approvals_required": 2,
        "approvals_done": 1
    }
}

One approval is not enough: the milestone stays evidence_submitted and approvals_done is 1 of 2. A second, different person decides:

bash
curl -X POST "$BASE/v1/enterprise/approvals/55555551-5555-4555-8555-555555555555/decide" \
  -H "Authorization: Bearer $ADMIN_KEY" \
  -H "X-Enterprise-Account: $WORKSPACE_ID" \
  -H "Idempotency-Key: fffffff7-ffff-4fff-8fff-ffffffffffff" \
  -H "Content-Type: application/json" \
  -d '{ "decision": "approved" }'
json
{
    "success": true,
    "data": {
        "milestone_id": "bbbbbbb5-bbbb-4bbb-8bbb-bbbbbbbbbbbb",
        "approval_id": "66666666-6666-4666-8666-666666666666",
        "decision": "approved",
        "milestone_status": "approved",
        "approvals_required": 2,
        "approvals_done": 2
    }
}

The milestone is approved. A decision is final once saved. The person who submitted the evidence cannot decide (409 SUBMITTER_CANNOT_APPROVE for finance and above, 403 FORBIDDEN for an operator), and the same person cannot decide twice (409 ALREADY_DECIDED).

9. Release with splits ​

Release pays the milestone to up to five verified payout instructions. The amounts must add up to the milestone amount, exactly.

bash
curl -X POST "$BASE/v1/enterprise/deals/33333331-3333-4333-8333-333333333333/milestones/55555551-5555-4555-8555-555555555555/release" \
  -H "Authorization: Bearer $FINANCE_KEY" \
  -H "X-Enterprise-Account: $WORKSPACE_ID" \
  -H "Idempotency-Key: 77777776-7777-4777-8777-777777777777" \
  -H "Content-Type: application/json" \
  -d '{
    "splits": [
      { "payout_instruction_id": "77777771-7777-4777-8777-777777777777", "amount": 100000000 },
      { "payout_instruction_id": "88888881-8888-4888-8888-888888888888", "amount": 50000000 }
    ]
  }'
json
{
    "success": true,
    "data": {
        "milestone_id": "bbbbbbb5-bbbb-4bbb-8bbb-bbbbbbbbbbbb",
        "status": "released",
        "deal_status": "in_progress",
        "disbursements": [
            {
                "id": "00000006-0000-4000-8000-000000000000",
                "payout_instruction_id": "aaaaaaa6-aaaa-4aaa-8aaa-aaaaaaaaaaaa",
                "amount": 100000000,
                "currency": "IDR",
                "status": "queued"
            },
            {
                "id": "bbbbbbb6-bbbb-4bbb-8bbb-bbbbbbbbbbbb",
                "payout_instruction_id": "ccccccc6-cccc-4ccc-8ccc-cccccccccccc",
                "amount": 50000000,
                "currency": "IDR",
                "status": "queued"
            }
        ]
    }
}

Release is final. Each split is a queued disbursement that RekberPay settles. Splits that do not add up are refused and nothing is released:

json
{
    "success": false,
    "error": {
        "code": "SPLITS_MISMATCH",
        "message": "Splits must add up to the milestone amount."
    }
}

10. Poll the event feed ​

Pass your cursor as after. deal_id narrows the feed to this deal.

bash
curl "$BASE/v1/enterprise/events?after=<cursor from step 3>&deal_id=33333331-3333-4333-8333-333333333333&limit=100" \
  -H "Authorization: Bearer $FINANCE_KEY" \
  -H "X-Enterprise-Account: $WORKSPACE_ID"

The page below held 12 events, from deal.created to the last disbursement.queued. It is cut to the first 2 and the last event here, and next_after is the highest seq of the full page. Store it and send it as after on the next poll. When a page comes back with limit items, poll again straight away.

json
{
    "success": true,
    "data": {
        "items": [
            {
                "seq": 135673,
                "id": "88888887-8888-4888-8888-888888888888",
                "event": "deal.created",
                "entity_type": "deal",
                "entity_id": "88888885-8888-4888-8888-888888888888",
                "deal_id": "88888885-8888-4888-8888-888888888888",
                "deal_reference": "DEAL-2026-0009",
                "actor": {
                    "id": "99999995-9999-4999-8999-999999999999",
                    "name": "Sari Wibowo"
                },
                "actor_label": null,
                "amount": 260000000,
                "summary": "Created deal DEAL-2026-0009: Electronic control boards, lot 8",
                "created_at": "2026-10-13T03:23:04.418Z",
                "livemode": false
            },
            {
                "seq": 135674,
                "id": "99999997-9999-4999-8999-999999999999",
                "event": "deal.activated",
                "entity_type": "deal",
                "entity_id": "88888885-8888-4888-8888-888888888888",
                "deal_id": "88888885-8888-4888-8888-888888888888",
                "deal_reference": "DEAL-2026-0009",
                "actor": {
                    "id": "99999995-9999-4999-8999-999999999999",
                    "name": "Sari Wibowo"
                },
                "actor_label": null,
                "amount": null,
                "summary": "Activated deal DEAL-2026-0009",
                "created_at": "2026-10-13T03:23:04.510Z",
                "livemode": false
            }
        ],
        "next_after": 135684
    }
}

RekberPay does not push enterprise events to your server. Poll every 15 to 60 seconds. Each event has a seq that only goes up, so you never miss one and can safely re-read from the last seq you processed.

11. Check the result ​

bash
curl "$BASE/v1/enterprise/deals/33333331-3333-4333-8333-333333333333" \
  -H "Authorization: Bearer $FINANCE_KEY" \
  -H "X-Enterprise-Account: $WORKSPACE_ID"

The deal is in_progress (one of three milestones released), the released milestone shows both disbursements, and outcome summarises the run. Cut to the deal and outcome:

json
{
    "success": true,
    "data": {
        "deal": {
            "id": "88888885-8888-4888-8888-888888888888",
            "reference": "DEAL-2026-0009",
            "title": "Electronic control boards, lot 8",
            "description": null,
            "external_ref": "PO-2026-0188",
            "status": "in_progress",
            "currency": "IDR",
            "total_amount": 260000000,
            "funded_amount": 150000000,
            "released_amount": 150000000,
            "cross_border": true,
            "owner": {
                "id": "99999995-9999-4999-8999-999999999999",
                "name": "Sari Wibowo"
            },
            "created_at": "2026-10-13T03:23:04.417Z",
            "updated_at": "2026-10-13T03:23:04.739Z",
            "counterparty": {
                "id": "00000005-0000-4000-8000-000000000000",
                "name": "Harbourline Components Pte Ltd",
                "country_code": "SG",
                "kyb_status": "verified",
                "relationship": "seller"
            }
        },
        "outcome": {
            "time_to_release_minutes": 0,
            "approvals_by_people": 2,
            "revisions": 0
        }
    }
}

Payouts you queued, newest first:

bash
curl "$BASE/v1/enterprise/payouts?status=queued&limit=2" \
  -H "Authorization: Bearer $FINANCE_KEY" \
  -H "X-Enterprise-Account: $WORKSPACE_ID"
json
{
    "success": true,
    "data": {
        "items": [
            {
                "id": "bbbbbbb6-bbbb-4bbb-8bbb-bbbbbbbbbbbb",
                "deal_id": "88888885-8888-4888-8888-888888888888",
                "deal_reference": "DEAL-2026-0009",
                "milestone_id": "bbbbbbb5-bbbb-4bbb-8bbb-bbbbbbbbbbbb",
                "milestone_title": "Goods in transit",
                "payout_instruction_id": "ccccccc6-cccc-4ccc-8ccc-cccccccccccc",
                "instruction_label": "Harbourline freight sub-account",
                "bank_name": "DBS Bank Indonesia",
                "account_number_masked": "3320****99",
                "amount": 50000000,
                "currency": "IDR",
                "status": "queued",
                "failure_reason": null,
                "settled_at": null,
                "created_at": "2026-10-13T03:23:04.739Z",
                "updated_at": "2026-10-13T03:23:04.739Z"
            },
            {
                "id": "00000006-0000-4000-8000-000000000000",
                "deal_id": "88888885-8888-4888-8888-888888888888",
                "deal_reference": "DEAL-2026-0009",
                "milestone_id": "bbbbbbb5-bbbb-4bbb-8bbb-bbbbbbbbbbbb",
                "milestone_title": "Goods in transit",
                "payout_instruction_id": "aaaaaaa6-aaaa-4aaa-8aaa-aaaaaaaaaaaa",
                "instruction_label": "Harbourline IDR collection account",
                "bank_name": "DBS Bank Indonesia",
                "account_number_masked": "3320****43",
                "amount": 100000000,
                "currency": "IDR",
                "status": "queued",
                "failure_reason": null,
                "settled_at": null,
                "created_at": "2026-10-13T03:23:04.739Z",
                "updated_at": "2026-10-13T03:23:04.739Z"
            }
        ],
        "summary": {
            "today_count": 4,
            "today_amount": 253000000,
            "avg_settlement_minutes": 32,
            "queued_amount": 178000000
        }
    },
    "pagination": {
        "page": 1,
        "limit": 2,
        "total": 3,
        "total_pages": 2
    }
}

And the books balance:

bash
curl "$BASE/v1/enterprise/reconciliation" \
  -H "Authorization: Bearer $FINANCE_KEY" \
  -H "X-Enterprise-Account: $WORKSPACE_ID"
json
{
    "success": true,
    "data": {
        "deals": [
            {
                "id": "88888885-8888-4888-8888-888888888888",
                "reference": "DEAL-2026-0009",
                "stored_funded": 150000000,
                "computed_funded": 150000000,
                "stored_released": 150000000,
                "computed_released": 150000000,
                "balanced": true
            }
        ],
        "balanced_all": true,
        "truncated": false,
        "checked_at": "2026-10-13T03:23:04.848Z"
    }
}

12. Export the audit log ​

The CSV export needs the finance role or above. Dates are YYYY-MM-DD and the default window is the last 90 days. The ip column only appears for admins and owners; a finance caller gets the same file without it.

bash
curl -o audit.csv \
  "$BASE/v1/enterprise/audit/export.csv?from=2026-10-13&to=2026-10-13" \
  -H "Authorization: Bearer $FINANCE_KEY" \
  -H "X-Enterprise-Account: $WORKSPACE_ID"

The response is text/csv with a Content-Disposition filename. These are the header and the three rows for the release:

csv
seq,created_at,event,actor,deal,amount,summary,previous_state,new_state
135682,2026-10-13T03:23:04.739Z,milestone.released,Maya Santoso,DEAL-2026-0009,150000000.00,Released DEAL-2026-0009 / Goods in transit in 2 payouts,"{""status"":""approved""}","{""status"":""released"",""splits"":[{""disbursement_id"":""00000001-0000-4000-8000-000000000000"",""payout_instruction_id"":""77777771-7777-4777-8777-777777777777"",""amount"":100000000},{""disbursement_id"":""aaaaaaa1-aaaa-4aaa-8aaa-aaaaaaaaaaaa"",""payout_instruction_id"":""88888881-8888-4888-8888-888888888888"",""amount"":50000000}]}"
135683,2026-10-13T03:23:04.740Z,disbursement.queued,Maya Santoso,DEAL-2026-0009,100000000.00,Payout queued for DEAL-2026-0009 / Goods in transit,,"{""status"":""queued"",""payout_instruction_id"":""77777771-7777-4777-8777-777777777777""}"
135684,2026-10-13T03:23:04.740Z,disbursement.queued,Maya Santoso,DEAL-2026-0009,50000000.00,Payout queued for DEAL-2026-0009 / Goods in transit,,"{""status"":""queued"",""payout_instruction_id"":""88888881-8888-4888-8888-888888888888""}"

One export holds at most 10,000 rows. When there were more, the response carries X-Export-Truncated: true; export again with a narrower date range.

The whole flow in one script ​

The same steps as a script. It reads the three keys and the workspace id from the environment, and reads the base URL from REKBERPAY_BASE_URL (default https://api.rekberpay.com). Run it only against a sandbox workspace: it creates a real deal each time.

js
import { randomUUID } from 'node:crypto';

const BASE = process.env.REKBERPAY_BASE_URL ?? 'https://api.rekberpay.com';
const WORKSPACE_ID = process.env.WORKSPACE_ID;
const KEYS = {
    operator: process.env.OPERATOR_KEY,
    finance: process.env.FINANCE_KEY,
    admin: process.env.ADMIN_KEY,
};

async function api(role, method, path, body, { idempotent = false } = {}) {
    const headers = {
        Authorization: `Bearer ${KEYS[role]}`,
        'X-Enterprise-Account': WORKSPACE_ID,
    };
    if (method !== 'GET') headers['Content-Type'] = 'application/json';
    if (idempotent) headers['Idempotency-Key'] = randomUUID();
    const res = await fetch(`${BASE}/v1/enterprise${path}`, {
        method,
        headers,
        body: method === 'GET' ? undefined : JSON.stringify(body ?? {}),
    });
    const json = await res.json();
    if (!res.ok) throw new Error(`${method} ${path} failed: ${res.status} ${json.error?.code}`);
    return json.data;
}

// 1. Find the counterparty and two verified payout instructions for it.
const [counterparty] = await api('finance', 'GET', '/counterparties?q=Harbourline&kyb_status=verified');
const instructions = await api(
    'finance',
    'GET',
    `/payout-instructions?counterparty_id=${counterparty.id}&status=verified`,
);

// 2. Remember where the event feed is now, so we only read what happens next.
const { next_after: cursor } = await api('finance', 'GET', '/events?order=desc&limit=1');

// 3. Create and activate a deal (operator).
const created = await api(
    'operator',
    'POST',
    '/deals',
    {
        title: 'Electronic control boards, lot 8',
        external_ref: 'PO-2026-0188',
        counterparty_id: counterparty.id,
        currency: 'IDR',
        cross_border: true,
        milestones: [
            { title: 'PO signed and registered', amount: 50000000, required_evidence: 'Countersigned PO' },
            { title: 'Goods in transit', amount: 150000000, required_evidence: 'Bill of lading and export declaration' },
            { title: 'Final delivery', amount: 60000000 },
        ],
    },
    { idempotent: true },
);
const dealId = created.deal.id;
const milestoneId = created.milestones[1].id;
await api('operator', 'POST', `/deals/${dealId}/activate`, {}, { idempotent: true });

// 4. Fund the milestone (finance, sandbox workspaces only).
await api(
    'finance',
    'POST',
    `/deals/${dealId}/milestones/${milestoneId}/fund`,
    { method: 'sandbox', reference: 'TEST-001' },
    { idempotent: true },
);

// 5. Register the evidence file and submit it (operator).
const doc = await api('operator', 'POST', '/documents', {
    title: 'Bill of lading HLCU-5521',
    document_type: 'shipping',
    file_url: 'https://files.northline.example/bl/HLCU-5521.pdf',
    deal_id: dealId,
    milestone_id: milestoneId,
});
await api(
    'operator',
    'POST',
    `/deals/${dealId}/milestones/${milestoneId}/submit-evidence`,
    {
        evidence_summary: 'Bill of lading HLCU-5521 and the export declaration for all 600 boards.',
        document_ids: [doc.id],
    },
    { idempotent: true },
);

// 6. Two different approvers decide (the amount is at or above the four-eyes threshold).
for (const role of ['finance', 'admin']) {
    const decision = await api(
        role,
        'POST',
        `/approvals/${milestoneId}/decide`,
        { decision: 'approved' },
        { idempotent: true },
    );
    console.log(role, 'approved:', decision.approvals_done, 'of', decision.approvals_required);
}

// 7. Release to two payout instructions. The splits must add up to the milestone amount.
const released = await api(
    'finance',
    'POST',
    `/deals/${dealId}/milestones/${milestoneId}/release`,
    {
        splits: [
            { payout_instruction_id: instructions[0].id, amount: 100000000 },
            { payout_instruction_id: instructions[1].id, amount: 50000000 },
        ],
    },
    { idempotent: true },
);
console.log('released:', released.status, released.disbursements.length, 'payouts');

// 8. Read the feed from the cursor.
const feed = await api('finance', 'GET', `/events?after=${cursor}&deal_id=${dealId}&limit=100`);
for (const event of feed.items) console.log(event.seq, event.event);
console.log('next cursor:', feed.next_after);
python
import os
import uuid

import requests

BASE = os.environ.get("REKBERPAY_BASE_URL", "https://api.rekberpay.com")
WORKSPACE_ID = os.environ["WORKSPACE_ID"]
KEYS = {
    "operator": os.environ["OPERATOR_KEY"],
    "finance": os.environ["FINANCE_KEY"],
    "admin": os.environ["ADMIN_KEY"],
}


def api(role, method, path, body=None, idempotent=False):
    headers = {
        "Authorization": f"Bearer {KEYS[role]}",
        "X-Enterprise-Account": WORKSPACE_ID,
    }
    if idempotent:
        headers["Idempotency-Key"] = str(uuid.uuid4())
    res = requests.request(
        method,
        f"{BASE}/v1/enterprise{path}",
        headers=headers,
        json=None if method == "GET" else (body or {}),
        timeout=30,
    )
    payload = res.json()
    if not res.ok:
        raise RuntimeError(f"{method} {path} failed: {res.status_code} {payload['error']['code']}")
    return payload["data"]


# 1. Find the counterparty and two verified payout instructions for it.
counterparty = api("finance", "GET", "/counterparties?q=Harbourline&kyb_status=verified")[0]
instructions = api(
    "finance", "GET", f"/payout-instructions?counterparty_id={counterparty['id']}&status=verified"
)

# 2. Remember where the event feed is now, so we only read what happens next.
cursor = api("finance", "GET", "/events?order=desc&limit=1")["next_after"]

# 3. Create and activate a deal (operator).
created = api(
    "operator",
    "POST",
    "/deals",
    {
        "title": "Electronic control boards, lot 8",
        "external_ref": "PO-2026-0188",
        "counterparty_id": counterparty["id"],
        "currency": "IDR",
        "cross_border": True,
        "milestones": [
            {"title": "PO signed and registered", "amount": 50000000, "required_evidence": "Countersigned PO"},
            {"title": "Goods in transit", "amount": 150000000, "required_evidence": "Bill of lading and export declaration"},
            {"title": "Final delivery", "amount": 60000000},
        ],
    },
    idempotent=True,
)
deal_id = created["deal"]["id"]
milestone_id = created["milestones"][1]["id"]
api("operator", "POST", f"/deals/{deal_id}/activate", idempotent=True)

# 4. Fund the milestone (finance, sandbox workspaces only).
api(
    "finance",
    "POST",
    f"/deals/{deal_id}/milestones/{milestone_id}/fund",
    {"method": "sandbox", "reference": "TEST-001"},
    idempotent=True,
)

# 5. Register the evidence file and submit it (operator).
doc = api(
    "operator",
    "POST",
    "/documents",
    {
        "title": "Bill of lading HLCU-5521",
        "document_type": "shipping",
        "file_url": "https://files.northline.example/bl/HLCU-5521.pdf",
        "deal_id": deal_id,
        "milestone_id": milestone_id,
    },
)
api(
    "operator",
    "POST",
    f"/deals/{deal_id}/milestones/{milestone_id}/submit-evidence",
    {
        "evidence_summary": "Bill of lading HLCU-5521 and the export declaration for all 600 boards.",
        "document_ids": [doc["id"]],
    },
    idempotent=True,
)

# 6. Two different approvers decide (the amount is at or above the four-eyes threshold).
for role in ("finance", "admin"):
    decision = api(role, "POST", f"/approvals/{milestone_id}/decide", {"decision": "approved"}, idempotent=True)
    print(role, "approved:", decision["approvals_done"], "of", decision["approvals_required"])

# 7. Release to two payout instructions. The splits must add up to the milestone amount.
released = api(
    "finance",
    "POST",
    f"/deals/{deal_id}/milestones/{milestone_id}/release",
    {
        "splits": [
            {"payout_instruction_id": instructions[0]["id"], "amount": 100000000},
            {"payout_instruction_id": instructions[1]["id"], "amount": 50000000},
        ]
    },
    idempotent=True,
)
print("released:", released["status"], len(released["disbursements"]), "payouts")

# 8. Read the feed from the cursor.
feed = api("finance", "GET", f"/events?after={cursor}&deal_id={deal_id}&limit=100")
for event in feed["items"]:
    print(event["seq"], event["event"])
print("next cursor:", feed["next_after"])
php
<?php
$base = getenv('REKBERPAY_BASE_URL') ?: 'https://api.rekberpay.com';
$workspaceId = getenv('WORKSPACE_ID');
$keys = [
    'operator' => getenv('OPERATOR_KEY'),
    'finance' => getenv('FINANCE_KEY'),
    'admin' => getenv('ADMIN_KEY'),
];

function uuid_v4(): string {
    $b = random_bytes(16);
    $b[6] = chr((ord($b[6]) & 0x0f) | 0x40);
    $b[8] = chr((ord($b[8]) & 0x3f) | 0x80);
    return vsprintf('%s%s-%s-%s-%s-%s%s%s', str_split(bin2hex($b), 4));
}

function api(string $role, string $method, string $path, ?array $body = null, bool $idempotent = false): array {
    global $base, $workspaceId, $keys;
    $headers = [
        'Authorization: Bearer ' . $keys[$role],
        'X-Enterprise-Account: ' . $workspaceId,
    ];
    if ($method !== 'GET') {
        $headers[] = 'Content-Type: application/json';
    }
    if ($idempotent) {
        $headers[] = 'Idempotency-Key: ' . uuid_v4();
    }
    $ch = curl_init($base . '/v1/enterprise' . $path);
    curl_setopt_array($ch, [
        CURLOPT_CUSTOMREQUEST => $method,
        CURLOPT_HTTPHEADER => $headers,
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_TIMEOUT => 30,
    ]);
    if ($method !== 'GET') {
        curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($body ?? new stdClass()));
    }
    $raw = curl_exec($ch);
    $status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
    $payload = json_decode($raw, true);
    if ($status >= 400) {
        throw new RuntimeException("$method $path failed: $status " . ($payload['error']['code'] ?? ''));
    }
    return $payload['data'];
}

// 1. Find the counterparty and two verified payout instructions for it.
$counterparty = api('finance', 'GET', '/counterparties?q=Harbourline&kyb_status=verified')[0];
$instructions = api('finance', 'GET', '/payout-instructions?counterparty_id=' . $counterparty['id'] . '&status=verified');

// 2. Remember where the event feed is now, so we only read what happens next.
$cursor = api('finance', 'GET', '/events?order=desc&limit=1')['next_after'];

// 3. Create and activate a deal (operator).
$created = api('operator', 'POST', '/deals', [
    'title' => 'Electronic control boards, lot 8',
    'external_ref' => 'PO-2026-0188',
    'counterparty_id' => $counterparty['id'],
    'currency' => 'IDR',
    'cross_border' => true,
    'milestones' => [
        ['title' => 'PO signed and registered', 'amount' => 50000000, 'required_evidence' => 'Countersigned PO'],
        ['title' => 'Goods in transit', 'amount' => 150000000, 'required_evidence' => 'Bill of lading and export declaration'],
        ['title' => 'Final delivery', 'amount' => 60000000],
    ],
], true);
$dealId = $created['deal']['id'];
$milestoneId = $created['milestones'][1]['id'];
api('operator', 'POST', "/deals/$dealId/activate", null, true);

// 4. Fund the milestone (finance, sandbox workspaces only).
api('finance', 'POST', "/deals/$dealId/milestones/$milestoneId/fund", [
    'method' => 'sandbox',
    'reference' => 'TEST-001',
], true);

// 5. Register the evidence file and submit it (operator).
$doc = api('operator', 'POST', '/documents', [
    'title' => 'Bill of lading HLCU-5521',
    'document_type' => 'shipping',
    'file_url' => 'https://files.northline.example/bl/HLCU-5521.pdf',
    'deal_id' => $dealId,
    'milestone_id' => $milestoneId,
]);
api('operator', 'POST', "/deals/$dealId/milestones/$milestoneId/submit-evidence", [
    'evidence_summary' => 'Bill of lading HLCU-5521 and the export declaration for all 600 boards.',
    'document_ids' => [$doc['id']],
], true);

// 6. Two different approvers decide (the amount is at or above the four-eyes threshold).
foreach (['finance', 'admin'] as $role) {
    $decision = api($role, 'POST', "/approvals/$milestoneId/decide", ['decision' => 'approved'], true);
    echo "$role approved: {$decision['approvals_done']} of {$decision['approvals_required']}\n";
}

// 7. Release to two payout instructions. The splits must add up to the milestone amount.
$released = api('finance', 'POST', "/deals/$dealId/milestones/$milestoneId/release", [
    'splits' => [
        ['payout_instruction_id' => $instructions[0]['id'], 'amount' => 100000000],
        ['payout_instruction_id' => $instructions[1]['id'], 'amount' => 50000000],
    ],
], true);
echo "released: {$released['status']} " . count($released['disbursements']) . " payouts\n";

// 8. Read the feed from the cursor.
$feed = api('finance', 'GET', "/events?after=$cursor&deal_id=$dealId&limit=100");
foreach ($feed['items'] as $event) {
    echo $event['seq'] . ' ' . $event['event'] . "\n";
}
echo 'next cursor: ' . $feed['next_after'] . "\n";

The first two payout instructions it finds for Harbourline must be verified. A successful run prints something like this (sequence numbers differ in your workspace):

text
finance approved: 1 of 2
admin approved: 2 of 2
released: released 2 payouts
135698 deal.created
135699 deal.activated
135700 milestone.funded
135701 document.added
135702 milestone.evidence_submitted
135703 approval.requested
135704 approval.decided
135705 approval.decided
135706 milestone.approved
135707 milestone.released
135708 disbursement.queued
135709 disbursement.queued
next cursor: 135709

When something goes wrong ​

Every error uses the same envelope. These are the ones you are most likely to hit in this flow:

StatusCodeWhat to do
400INVALID_BODYA field is missing or out of range. error.details.fieldErrors names it
400SPLITS_MISMATCHMake the split amounts add up to the milestone amount
403FORBIDDENThe key's user has too low a role for this step
403NO_WORKSPACEThe key's user is not an active member of the workspace in X-Enterprise-Account
403LIVE_FUNDING_IS_PLATFORM_CONFIRMEDThe workspace is live, so do not call fund
409ALREADY_DECIDEDYou already decided on this milestone
409SUBMITTER_CANNOT_APPROVEAsk someone else to decide
409NOT_APPROVEDRelease only works on an approved milestone
422IDEMPOTENCY_KEY_CONFLICTYou reused a key with a different body. Use a new key
429RATE_LIMITEDWait Retry-After seconds and retry

The full list is in the Errors reference and Errors.

Escrow, payment services and B2B deals for Indonesian businesses.