Skip to content

Four-eyes approvals ​

A workspace has a four_eyes_threshold in its settings. Any milestone with an amount at or above it needs two approvals from two different people before it can be released. The person who submitted the evidence can never be one of them. Below the threshold one approval is enough.

1. Check the threshold ​

bash
curl https://api.rekberpay.com/v1/enterprise/settings \
  -H "Authorization: Bearer $REKBERPAY_WORKSPACE_KEY"
js
const res = await fetch('https://api.rekberpay.com/v1/enterprise/settings', {
    headers: {
        Authorization: `Bearer ${process.env.REKBERPAY_WORKSPACE_KEY}`,
    },
});
const data = await res.json();
python
import os

import requests

resp = requests.get(
    "https://api.rekberpay.com/v1/enterprise/settings",
    headers={
        "Authorization": f"Bearer {os.environ['REKBERPAY_WORKSPACE_KEY']}",
    },
    timeout=30,
)
print(resp.status_code, resp.json())
php
<?php

$ch = curl_init('https://api.rekberpay.com/v1/enterprise/settings');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('REKBERPAY_WORKSPACE_KEY'),
    ],
]);
$response = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
curl_close($ch);

200 OK

json
{
    "success": true,
    "data": {
        "display_name": "Northline Trading",
        "legal_name": "PT Northline Trading Indonesia",
        "billing_email": "[email protected]",
        "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]"
            }
        }
    }
}

Admins change it with PATCH /v1/enterprise/settings.

2. A milestone above the threshold ​

This run creates a deal with one milestone of Rp150.000.000, above the Rp100.000.000 threshold:

bash
curl -X POST https://api.rekberpay.com/v1/enterprise/deals \
  -H "Authorization: Bearer $REKBERPAY_WORKSPACE_KEY" \
  -H "Idempotency-Key: 33333337-3333-4333-8333-333333333333" \
  -H "Content-Type: application/json" \
  -d '{
    "title": "Precision moulds, batch 7",
    "counterparty_id": "eeeeeee3-eeee-4eee-8eee-eeeeeeeeeeee",
    "milestones": [
      {
        "title": "Moulds delivered and inspected",
        "amount": 150000000,
        "required_evidence": "Inspection report"
      }
    ]
  }'
js
const res = await fetch('https://api.rekberpay.com/v1/enterprise/deals', {
    method: 'POST',
    headers: {
        Authorization: `Bearer ${process.env.REKBERPAY_WORKSPACE_KEY}`,
        'Content-Type': 'application/json',
        'Idempotency-Key': '33333337-3333-4333-8333-333333333333',
    },
    body: JSON.stringify({
        title: "Precision moulds, batch 7",
        counterparty_id: "eeeeeee3-eeee-4eee-8eee-eeeeeeeeeeee",
        milestones: [
            {
                title: "Moulds delivered and inspected",
                amount: 150000000,
                required_evidence: "Inspection report"
            }
        ]
    }),
});
const data = await res.json();
python
import os

import requests

resp = requests.post(
    "https://api.rekberpay.com/v1/enterprise/deals",
    headers={
        "Authorization": f"Bearer {os.environ['REKBERPAY_WORKSPACE_KEY']}",
        "Idempotency-Key": "33333337-3333-4333-8333-333333333333",
    },
    json={
        "title": "Precision moulds, batch 7",
        "counterparty_id": "eeeeeee3-eeee-4eee-8eee-eeeeeeeeeeee",
        "milestones": [
            {
                "title": "Moulds delivered and inspected",
                "amount": 150000000,
                "required_evidence": "Inspection report",
            },
        ],
    },
    timeout=30,
)
print(resp.status_code, resp.json())
php
<?php

$ch = curl_init('https://api.rekberpay.com/v1/enterprise/deals');
curl_setopt_array($ch, [
    CURLOPT_CUSTOMREQUEST => 'POST',
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('REKBERPAY_WORKSPACE_KEY'),
        'Content-Type: application/json',
        'Idempotency-Key: 33333337-3333-4333-8333-333333333333',
    ],
    CURLOPT_POSTFIELDS => json_encode([
        'title' => 'Precision moulds, batch 7',
        'counterparty_id' => 'eeeeeee3-eeee-4eee-8eee-eeeeeeeeeeee',
        'milestones' => [
            [
                'title' => 'Moulds delivered and inspected',
                'amount' => 150000000,
                'required_evidence' => 'Inspection report',
            ],
        ],
    ]),
]);
$response = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
curl_close($ch);

After it is activated and funded, an operator submits the evidence. The answer says two approvals are needed:

200 OK

json
{
    "success": true,
    "data": {
        "milestone_id": "55555557-5555-4555-8555-555555555555",
        "status": "evidence_submitted",
        "approvals_required": 2,
        "deal_status": "funded"
    }
}

3. The first approval ​

The approval detail shows the round:

bash
curl https://api.rekberpay.com/v1/enterprise/approvals/55555557-5555-4555-8555-555555555555 \
  -H "Authorization: Bearer $REKBERPAY_WORKSPACE_KEY"
js
const res = await fetch('https://api.rekberpay.com/v1/enterprise/approvals/55555557-5555-4555-8555-555555555555', {
    headers: {
        Authorization: `Bearer ${process.env.REKBERPAY_WORKSPACE_KEY}`,
    },
});
const data = await res.json();
python
import os

import requests

resp = requests.get(
    "https://api.rekberpay.com/v1/enterprise/approvals/55555557-5555-4555-8555-555555555555",
    headers={
        "Authorization": f"Bearer {os.environ['REKBERPAY_WORKSPACE_KEY']}",
    },
    timeout=30,
)
print(resp.status_code, resp.json())
php
<?php

$ch = curl_init('https://api.rekberpay.com/v1/enterprise/approvals/55555557-5555-4555-8555-555555555555');
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);

200 OK

json
{
    "success": true,
    "data": {
        "item": {
            "milestone_id": "55555557-5555-4555-8555-555555555555",
            "deal_id": "44444447-4444-4444-8444-444444444444",
            "deal_reference": "DEAL-2026-0011",
            "deal_title": "Precision moulds, batch 7",
            "milestone_title": "Moulds delivered and inspected",
            "status": "evidence_submitted",
            "amount": 150000000,
            "currency": "IDR",
            "counterparty_name": "Harbourline Components Pte Ltd",
            "risk_tier": "standard",
            "evidence_summary": "Moulds received and measured against drawing rev C.",
            "evidence_submitted_at": "2026-10-13T14:33:16.594Z",
            "due_at": null,
            "approvals_required": 2,
            "approvals_done": 0,
            "my_decision": null,
            "submitted_by": {
                "id": "88888884-8888-4888-8888-888888888888",
                "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": 140557,
                "event": "deal.created",
                "entity_type": "deal",
                "entity_id": "44444447-4444-4444-8444-444444444444",
                "actor": {
                    "id": "33333333-3333-4333-8333-333333333333",
                    "name": "Dimas Hidayat"
                },
                "amount": 150000000,
                "summary": "Created deal DEAL-2026-0011: Precision moulds, batch 7",
                "created_at": "2026-10-13T14:33:16.573Z"
            },
            {
                "seq": 140558,
                "event": "deal.activated",
                "entity_type": "deal",
                "entity_id": "44444447-4444-4444-8444-444444444444",
                "actor": {
                    "id": "33333333-3333-4333-8333-333333333333",
                    "name": "Dimas Hidayat"
                },
                "amount": null,
                "summary": "Activated deal DEAL-2026-0011",
                "created_at": "2026-10-13T14:33:16.582Z"
            }
        ],
        "impact": {
            "counterparty_paid": 150000000,
            "money_moved": 150000000,
            "milestones_remaining": 0,
            "deal_progress_after_pct": 100
        },
        "decision": {
            "risk_tier": "standard",
            "approvals_required": 2,
            "approvals": [
                {
                    "id": "77777777-7777-4777-8777-777777777777",
                    "approver": null,
                    "status": "pending",
                    "note": null,
                    "decided_at": null
                },
                {
                    "id": "88888887-8888-4888-8888-888888888888",
                    "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": []
    }
}

Lists are cut to the first 2 items and long text is shortened here.

The first approver decides. The milestone stays in evidence_submitted with approvals_done: 1:

bash
curl -X POST https://api.rekberpay.com/v1/enterprise/approvals/55555557-5555-4555-8555-555555555555/decide \
  -H "Authorization: Bearer $REKBERPAY_WORKSPACE_KEY" \
  -H "Idempotency-Key: 99999997-9999-4999-8999-999999999999" \
  -H "Content-Type: application/json" \
  -d '{
    "decision": "approved",
    "note": "Measurements within tolerance."
  }'
js
const res = await fetch('https://api.rekberpay.com/v1/enterprise/approvals/55555557-5555-4555-8555-555555555555/decide', {
    method: 'POST',
    headers: {
        Authorization: `Bearer ${process.env.REKBERPAY_WORKSPACE_KEY}`,
        'Content-Type': 'application/json',
        'Idempotency-Key': '99999997-9999-4999-8999-999999999999',
    },
    body: JSON.stringify({
        decision: "approved",
        note: "Measurements within tolerance."
    }),
});
const data = await res.json();
python
import os

import requests

resp = requests.post(
    "https://api.rekberpay.com/v1/enterprise/approvals/55555557-5555-4555-8555-555555555555/decide",
    headers={
        "Authorization": f"Bearer {os.environ['REKBERPAY_WORKSPACE_KEY']}",
        "Idempotency-Key": "99999997-9999-4999-8999-999999999999",
    },
    json={
        "decision": "approved",
        "note": "Measurements within tolerance.",
    },
    timeout=30,
)
print(resp.status_code, resp.json())
php
<?php

$ch = curl_init('https://api.rekberpay.com/v1/enterprise/approvals/55555557-5555-4555-8555-555555555555/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: 99999997-9999-4999-8999-999999999999',
    ],
    CURLOPT_POSTFIELDS => json_encode([
        'decision' => 'approved',
        'note' => 'Measurements within tolerance.',
    ]),
]);
$response = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
curl_close($ch);

200 OK

json
{
    "success": true,
    "data": {
        "milestone_id": "55555557-5555-4555-8555-555555555555",
        "approval_id": "77777777-7777-4777-8777-777777777777",
        "decision": "approved",
        "milestone_status": "evidence_submitted",
        "approvals_required": 2,
        "approvals_done": 1
    }
}

The same person cannot approve twice:

409 Conflict

json
{
    "success": false,
    "error": {
        "code": "ALREADY_DECIDED",
        "message": "You have already decided on this milestone."
    }
}

And nothing can be released yet:

409 Conflict

json
{
    "success": false,
    "error": {
        "code": "NOT_APPROVED",
        "message": "Only an approved milestone can be released."
    }
}

4. The second approval ​

A different finance member approves. The milestone becomes approved:

200 OK

json
{
    "success": true,
    "data": {
        "milestone_id": "55555557-5555-4555-8555-555555555555",
        "approval_id": "88888887-8888-4888-8888-888888888888",
        "decision": "approved",
        "milestone_status": "approved",
        "approvals_required": 2,
        "approvals_done": 2
    }
}

5. Release ​

bash
curl -X POST https://api.rekberpay.com/v1/enterprise/deals/44444447-4444-4444-8444-444444444444/milestones/55555557-5555-4555-8555-555555555555/release \
  -H "Authorization: Bearer $REKBERPAY_WORKSPACE_KEY" \
  -H "Idempotency-Key: bbbbbbb7-bbbb-4bbb-8bbb-bbbbbbbbbbbb" \
  -H "Content-Type: application/json" \
  -d '{
    "splits": [
      {
        "payout_instruction_id": "fffffff3-ffff-4fff-8fff-ffffffffffff",
        "amount": 150000000
      }
    ]
  }'
js
const res = await fetch('https://api.rekberpay.com/v1/enterprise/deals/44444447-4444-4444-8444-444444444444/milestones/55555557-5555-4555-8555-555555555555/release', {
    method: 'POST',
    headers: {
        Authorization: `Bearer ${process.env.REKBERPAY_WORKSPACE_KEY}`,
        'Content-Type': 'application/json',
        'Idempotency-Key': 'bbbbbbb7-bbbb-4bbb-8bbb-bbbbbbbbbbbb',
    },
    body: JSON.stringify({
        splits: [
            {
                payout_instruction_id: "fffffff3-ffff-4fff-8fff-ffffffffffff",
                amount: 150000000
            }
        ]
    }),
});
const data = await res.json();
python
import os

import requests

resp = requests.post(
    "https://api.rekberpay.com/v1/enterprise/deals/44444447-4444-4444-8444-444444444444/milestones/55555557-5555-4555-8555-555555555555/release",
    headers={
        "Authorization": f"Bearer {os.environ['REKBERPAY_WORKSPACE_KEY']}",
        "Idempotency-Key": "bbbbbbb7-bbbb-4bbb-8bbb-bbbbbbbbbbbb",
    },
    json={
        "splits": [
            {
                "payout_instruction_id": "fffffff3-ffff-4fff-8fff-ffffffffffff",
                "amount": 150000000,
            },
        ],
    },
    timeout=30,
)
print(resp.status_code, resp.json())
php
<?php

$ch = curl_init('https://api.rekberpay.com/v1/enterprise/deals/44444447-4444-4444-8444-444444444444/milestones/55555557-5555-4555-8555-555555555555/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: bbbbbbb7-bbbb-4bbb-8bbb-bbbbbbbbbbbb',
    ],
    CURLOPT_POSTFIELDS => json_encode([
        'splits' => [
            [
                'payout_instruction_id' => 'fffffff3-ffff-4fff-8fff-ffffffffffff',
                'amount' => 150000000,
            ],
        ],
    ]),
]);
$response = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
curl_close($ch);

200 OK

json
{
    "success": true,
    "data": {
        "milestone_id": "55555557-5555-4555-8555-555555555555",
        "status": "released",
        "deal_status": "completed",
        "disbursements": [
            {
                "id": "ccccccc7-cccc-4ccc-8ccc-cccccccccccc",
                "payout_instruction_id": "fffffff3-ffff-4fff-8fff-ffffffffffff",
                "amount": 150000000,
                "currency": "IDR",
                "status": "queued"
            }
        ]
    }
}

The rules ​

RuleError
Only finance, admin and owner can decide403 FORBIDDEN
The evidence submitter cannot decide409 SUBMITTER_CANNOT_APPROVE
One decision per person per round409 ALREADY_DECIDED
Release needs the milestone to be approved409 NOT_APPROVED
A rejection or revision closes the round; resubmitting evidence starts a new one

A workspace API key decides as the admin who created it, so a key and that admin count as the same person. Give each approver their own login.

Next steps ​

Escrow, payment services and B2B deals for Indonesian businesses.