Enterprise KYB
A company signs up, gets a sandbox workspace at once, and verifies the business through one KYB application: the company details, the people, the bank account and the documents. RekberPay reviews it in 1 to 3 business days. On approval the workspace switches to Live by itself. The same application, opened from a verification link, verifies a counterparty. The flow is walked through in Verify your business. Auth, roles, the workspace header and the shared limits are explained in Enterprise overview.
Statuses
Application status | Meaning | Workspace kyb_status | Counterparty kyb_status |
|---|---|---|---|
draft | Being filled in, or reopened after changes were requested | not_started, or draft after a change request | not_started or pending |
submitted | Waiting for review | pending | pending |
changes_requested | A reviewer asked for changes; a new draft version holds change_requests | draft | pending |
approved | Verified. The workspace is Live | verified | verified |
rejected | Not approved. decision_reason says why; saving again starts a new version | rejected | rejected |
Every version is kept. version counts up each time changes are requested or a rejected application is started again.
Identity data
NIK and passport numbers are encrypted at rest (AES-256-GCM) and are never returned: answers carry id_number_last4 only. The company bank account comes back as account_number_last4. Documents are private files served only by 5 minute links signed for one user.
Endpoints
| Method | Path | Endpoint | Auth |
|---|---|---|---|
POST | /v1/enterprise/register | Create a workspace | Any signed-in user, dashboard only |
GET | /v1/enterprise/kyb | Get the KYB application | Any member; full view finance and up |
PATCH | /v1/enterprise/kyb | Save the draft | admin, dashboard only |
POST | /v1/enterprise/kyb/uploads | Upload a document file | admin, dashboard only |
POST | /v1/enterprise/kyb/documents | Attach a document | admin, dashboard only |
DELETE | /v1/enterprise/kyb/documents/:id | Remove a document | admin, dashboard only |
GET | /v1/enterprise/kyb/documents/:id/url | Get a document link | admin, dashboard only |
POST | /v1/enterprise/kyb/submit | Submit for review | admin, dashboard only |
POST | /v1/enterprise/counterparties/:id/kyb-invite | Send a counterparty verification link | admin, dashboard only |
GET | /v1/kyb/invite/:token | Open a verification link | The link's user, dashboard only |
PATCH | /v1/kyb/invite/:token | Save a counterparty draft | The link's user, dashboard only |
POST | /v1/kyb/invite/:token/uploads | Upload a counterparty document file | The link's user, dashboard only |
POST | /v1/kyb/invite/:token/documents | Attach a counterparty document | The link's user, dashboard only |
DELETE | /v1/kyb/invite/:token/documents/:id | Remove a counterparty document | The link's user, dashboard only |
GET | /v1/kyb/invite/:token/documents/:id/url | Get a counterparty document link | The link's user, dashboard only |
POST | /v1/kyb/invite/:token/submit | Submit a counterparty application | The link's user, dashboard only |
Create a workspace
Creates a workspace for your company with you as its owner. It starts in the sandbox with kyb_status: "not_started" and a draft KYB application (version 1) that already holds the legal name, entity type and NIB, so Get the KYB application opens half filled. One account can own at most 3 workspaces (an operator setting).
- Auth: Dashboard session of any signed-in user, no workspace needed. API keys get
403 KEY_NOT_ALLOWED. - Rate limit: 5 an hour per user and 10 an hour per IP address for this route, plus the shared 300 a minute.
- Idempotency: Not supported. A retry creates another workspace, up to the limit.
Body (JSON)
| Name | Type | Required | Description |
|---|---|---|---|
legal_name | string | Yes | 2 to 180 characters, as on the deed |
display_name | string | Yes | 2 to 120 characters, the name your team sees |
entity_type | string | Yes | pt, pt_perorangan, cv, firma, koperasi, ud or foreign |
nib | string | Yes | NIB, 13 digits. For foreign, the home registration number (3 to 40 characters) |
country_code | string | No | ISO country code, default ID. Must be ID for Indonesian entities and another country for foreign |
turnstile_token | string | When enabled | The Cloudflare Turnstile token from the sign-up form |
Example request
curl -X POST https://api.rekberpay.com/v1/enterprise/register \
-b cookies.txt \
-H "X-CSRF-Token: $CSRF_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"legal_name": "PT Sinar Laut Logistik",
"display_name": "Sinar Laut Logistik",
"entity_type": "pt",
"nib": "9120001234567",
"turnstile_token": "0.turnstile-token"
}'const res = await fetch('https://api.rekberpay.com/v1/enterprise/register', {
method: 'POST',
headers: {
Cookie: process.env.REKBERPAY_SESSION_COOKIE, // the session cookie from POST /v1/auth/login
'X-CSRF-Token': process.env.REKBERPAY_CSRF_TOKEN,
'Content-Type': 'application/json',
},
body: JSON.stringify({
"legal_name": "PT Sinar Laut Logistik",
"display_name": "Sinar Laut Logistik",
"entity_type": "pt",
"nib": "9120001234567",
"turnstile_token": "0.turnstile-token"
}),
});
const data = await res.json();import os
import requests
session = requests.Session()
# The session cookie from POST /v1/auth/login
session.cookies.set("__Host-rekber_session", os.environ["REKBERPAY_SESSION"], domain="api.rekberpay.com")
resp = session.post(
"https://api.rekberpay.com/v1/enterprise/register",
headers={
"X-CSRF-Token": os.environ["REKBERPAY_CSRF_TOKEN"],
},
json={
"legal_name": "PT Sinar Laut Logistik",
"display_name": "Sinar Laut Logistik",
"entity_type": "pt",
"nib": "9120001234567",
"turnstile_token": "0.turnstile-token"
},
timeout=30,
)
print(resp.status_code, resp.json())<?php
$ch = curl_init('https://api.rekberpay.com/v1/enterprise/register');
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_COOKIEJAR => 'cookies.txt',
CURLOPT_COOKIEFILE => 'cookies.txt',
CURLOPT_HTTPHEADER => [
'X-CSRF-Token: ' . getenv('REKBERPAY_CSRF_TOKEN'),
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => json_encode([
'legal_name' => 'PT Sinar Laut Logistik',
'display_name' => 'Sinar Laut Logistik',
'entity_type' => 'pt',
'nib' => '9120001234567',
'turnstile_token' => '0.turnstile-token',
]),
]);
$response = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
curl_close($ch);Example response
201 Created
{
"success": true,
"data": {
"account": {
"id": "8fc61a2b-dbf6-4070-a570-aaa2a6560d02",
"legal_name": "PT Sinar Laut Logistik MV6XWDGH",
"display_name": "Sinar Laut MV6XWDGH",
"country_code": "ID",
"kyb_status": "not_started",
"risk_tier": "standard",
"is_sandbox": true
},
"membership": {
"id": "1c3f191e-3287-4896-b6aa-05572d7ddb49",
"role": "owner"
},
"kyb_application": {
"id": "09c9e6db-ddb2-48b0-ad86-4122b24ebb8c",
"version": 1,
"status": "draft"
}
}
}Errors
| Status | Code | Cause |
|---|---|---|
400 | INVALID_BODY | A field is missing or has the wrong format, see error.details |
403 | TURNSTILE_FAILED | The Turnstile check did not pass |
403 | KEY_NOT_ALLOWED | Called with an API key instead of a dashboard session |
409 | WORKSPACE_LIMIT | You already own the most workspaces one account may own |
409 | TRY_AGAIN | Another sign-up for your account is still running |
401 | UNAUTHORIZED, SESSION_EXPIRED, INVALID_TOKEN | No session or key, the session expired, or the key is wrong or revoked |
403 | CSRF_FAILED | Session call without the matching X-CSRF-Token header |
429 | RATE_LIMITED | Over the rate limit. Wait for Retry-After seconds |
Get the KYB application
The workspace's current application (the highest version), with progress, the document slots for its entity type and the history of earlier versions. Identity numbers come back as their last 4 digits only.
Owners, admins and finance get the full view (view: "full"). operator and viewer members and every API key get only the status (view: "status"): the subject's kyb_status and the application's status, version, submitted_at and decided_at.
- Auth: A workspace API key (status only), a personal API key or a dashboard session.
- Role: Any member. The full view needs
finance,adminorownerand a dashboard session. - Rate limit: 120 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/kyb \
-H "Authorization: Bearer $REKBERPAY_WORKSPACE_KEY"const res = await fetch('https://api.rekberpay.com/v1/enterprise/kyb', {
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/kyb",
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/kyb');
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": {
"view": "full",
"subject": {
"type": "account",
"id": "8fc61a2b-dbf6-4070-a570-aaa2a6560d02",
"name": "Sinar Laut MV6XWDGH",
"kyb_status": "draft",
"is_sandbox": true,
"account_manager": null
},
"application": {
"id": "61b95d77-3a11-4d25-ba85-27437db152ea",
"version": 2,
"status": "draft",
"entity_type": "pt",
"legal_name": "PT Sinar Laut Logistik MV6XWDGH",
"country_code": "ID",
"nib": "9120911597635",
"npwp": "012345678901000",
"address": "Jl. Pelabuhan Raya 12, Tanjung Priok, Jakarta Utara 14310",
"industry": "Logistics",
"monthly_volume": "1b_10b",
"website": "https://sinarlaut.example",
"bank": {
"bank_name": "BCA",
"bank_account_holder": "PT Sinar Laut Logistik MV6XWDGH",
"account_number_last4": "2219",
"has_account_number": true
},
"people": [
{
"id": "7d0392ef-c14d-4836-9ea6-2746d6f98486",
"full_name": "Ayu Pratiwi",
"position": "President Director",
"roles": [
"director",
"signer"
],
"id_type": "nik",
"id_number_last4": "0001",
"has_id_number": true,
"nationality": "ID",
"ownership_pct": 60
},
{
"id": "80e13305-314e-49c7-8068-c5666d60177e",
"full_name": "Bima Santoso",
"position": "Director",
"roles": [
"director",
"beneficial_owner"
],
"id_type": "nik",
"id_number_last4": "0002",
"has_id_number": true,
"nationality": "ID",
"ownership_pct": 40
}
],
"documents": [
{
"id": "c4682fbc-deb9-4978-8cc9-fc05b9d8e963",
"doc_type": "akta_pendirian",
"person_id": null,
"original_name": "akta_pendirian.pdf",
"mime": "application/pdf",
"size_bytes": 166,
"status": "uploaded",
"reviewer_note": null,
"created_at": "2026-10-13T17:13:17.843Z"
},
{
"id": "10a0d3f0-225e-48a8-bcf4-89abfbe14d9b",
"doc_type": "bank_proof",
"person_id": null,
"original_name": "bank_proof.pdf",
"mime": "application/pdf",
"size_bytes": 162,
"status": "uploaded",
"reviewer_note": null,
"created_at": "2026-10-13T17:13:17.843Z"
}
],
"change_requests": [
{
"item": "doc:npwp",
"label": "NPWP badan",
"reason": "The NPWP photo is blurred."
}
],
"submitted_at": null,
"decided_at": null,
"decision_reason": null,
"risk_tier_assigned": null,
"updated_at": "2026-10-13T17:13:17.842Z"
},
"progress": {
"steps": {
"company": true,
"people": true,
"bank": true,
"documents": false
},
"done": 3,
"total": 4,
"missing_documents": [
{
"key": "npwp",
"type": "npwp",
"label": "NPWP badan",
"hint": "The company tax card or certificate.",
"required": true,
"person_id": null
}
],
"complete": false
},
"slots": [
{
"key": "akta_pendirian",
"type": "akta_pendirian",
"label": "Akta pendirian",
"hint": "The deed of establishment.",
"required": true,
"person_id": null
},
{
"key": "akta_perubahan",
"type": "akta_perubahan",
"label": "Latest akta perubahan",
"hint": "The most recent amendment deed, if the company has one.",
"required": false,
"person_id": null
}
],
"history": [
{
"version": 2,
"status": "draft",
"submitted_at": null,
"decided_at": null,
"decision_reason": null
},
{
"version": 1,
"status": "changes_requested",
"submitted_at": "2026-10-13T17:13:17.801Z",
"decided_at": "2026-10-13T17:13:17.841Z",
"decision_reason": null
}
],
"review": {
"min_days": 1,
"max_days": 3,
"due_by": null
},
"can_edit": true
}
}Lists are cut to the first 2 items here.
Errors
| Status | Code | Cause |
|---|---|---|
400 | INVALID_ACCOUNT | X-Enterprise-Account is not a UUID |
403 | NO_WORKSPACE | You are not an active member of that workspace |
401 | UNAUTHORIZED, SESSION_EXPIRED, INVALID_TOKEN | No session or key, the session expired, or the key is wrong or revoked |
429 | RATE_LIMITED | Over the rate limit. Wait for Retry-After seconds |
Save the draft
Saves any of the three parts of the draft. Send only what changed: company fields are merged, people replaces the whole list (keep a person's id to keep their stored identity number when you leave id_number out), and bank fields are merged. Formats are checked on save: NIB 13 digits, NPWP 15 or 16 digits, NIK 16 digits with a valid province code and birth date, passport 6 to 12 letters and digits.
A submitted application cannot be edited (409 APPLICATION_LOCKED). After a rejection, the first save starts a new version from the rejected one.
- Auth: Dashboard session with
X-CSRF-Tokenon writes. API keys get403 KEY_NOT_ALLOWED: KYB is filled in by people, not integrations. - Role:
adminorowner. - Rate limit: 120 a minute per user for this route, plus the shared 300 a minute.
- Idempotency: Not needed. Saving the same body twice leaves the same draft.
Body (JSON)
| Name | Type | Required | Description |
|---|---|---|---|
company | object | No | entity_type, legal_name, country_code, nib, npwp, address, industry, monthly_volume (under_1b, 1b_10b, 10b_50b or over_50b), website |
people | array | No | Up to 20 people: id (keep to update), full_name, position, roles (director, signer, beneficial_owner), id_type (nik or passport), id_number, nationality, ownership_pct (25 to 100 for beneficial owners) |
bank | object | No | bank_name, bank_account_holder (should match the legal name), account_number |
Example request
curl -X PATCH https://api.rekberpay.com/v1/enterprise/kyb \
-b cookies.txt \
-H "X-CSRF-Token: $CSRF_TOKEN" \
-H "X-Enterprise-Account: $WORKSPACE_ID" \
-H "Content-Type: application/json" \
-d '{
"company": {
"npwp": "01.234.567.8-901.000",
"address": "Jl. Pelabuhan Raya 12, Tanjung Priok, Jakarta Utara 14310",
"industry": "Logistics",
"monthly_volume": "1b_10b"
},
"people": [
{
"full_name": "Ayu Pratiwi",
"position": "President Director",
"roles": [
"director",
"signer",
"beneficial_owner"
],
"id_type": "nik",
"id_number": "3171015505900001",
"ownership_pct": 60
}
],
"bank": {
"bank_name": "BCA",
"bank_account_holder": "PT Sinar Laut Logistik",
"account_number": "1234567890"
}
}'const res = await fetch('https://api.rekberpay.com/v1/enterprise/kyb', {
method: 'PATCH',
headers: {
Cookie: process.env.REKBERPAY_SESSION_COOKIE, // the session cookie from POST /v1/auth/login
'X-CSRF-Token': process.env.REKBERPAY_CSRF_TOKEN,
'X-Enterprise-Account': process.env.WORKSPACE_ID,
'Content-Type': 'application/json',
},
body: JSON.stringify({
"company": {
"npwp": "01.234.567.8-901.000",
"address": "Jl. Pelabuhan Raya 12, Tanjung Priok, Jakarta Utara 14310",
"industry": "Logistics",
"monthly_volume": "1b_10b"
},
"people": [
{
"full_name": "Ayu Pratiwi",
"position": "President Director",
"roles": [
"director",
"signer",
"beneficial_owner"
],
"id_type": "nik",
"id_number": "3171015505900001",
"ownership_pct": 60
}
],
"bank": {
"bank_name": "BCA",
"bank_account_holder": "PT Sinar Laut Logistik",
"account_number": "1234567890"
}
}),
});
const data = await res.json();import os
import requests
session = requests.Session()
# The session cookie from POST /v1/auth/login
session.cookies.set("__Host-rekber_session", os.environ["REKBERPAY_SESSION"], domain="api.rekberpay.com")
resp = session.patch(
"https://api.rekberpay.com/v1/enterprise/kyb",
headers={
"X-CSRF-Token": os.environ["REKBERPAY_CSRF_TOKEN"],
"X-Enterprise-Account": os.environ["WORKSPACE_ID"],
},
json={
"company": {
"npwp": "01.234.567.8-901.000",
"address": "Jl. Pelabuhan Raya 12, Tanjung Priok, Jakarta Utara 14310",
"industry": "Logistics",
"monthly_volume": "1b_10b"
},
"people": [
{
"full_name": "Ayu Pratiwi",
"position": "President Director",
"roles": [
"director",
"signer",
"beneficial_owner"
],
"id_type": "nik",
"id_number": "3171015505900001",
"ownership_pct": 60
}
],
"bank": {
"bank_name": "BCA",
"bank_account_holder": "PT Sinar Laut Logistik",
"account_number": "1234567890"
}
},
timeout=30,
)
print(resp.status_code, resp.json())<?php
$ch = curl_init('https://api.rekberpay.com/v1/enterprise/kyb');
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'PATCH',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_COOKIEJAR => 'cookies.txt',
CURLOPT_COOKIEFILE => 'cookies.txt',
CURLOPT_HTTPHEADER => [
'X-CSRF-Token: ' . getenv('REKBERPAY_CSRF_TOKEN'),
'X-Enterprise-Account: ' . getenv('WORKSPACE_ID'),
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => json_encode([
'company' => [
'npwp' => '01.234.567.8-901.000',
'address' => 'Jl. Pelabuhan Raya 12, Tanjung Priok, Jakarta Utara 14310',
'industry' => 'Logistics',
'monthly_volume' => '1b_10b',
],
'people' => [
[
'full_name' => 'Ayu Pratiwi',
'position' => 'President Director',
'roles' => [
'director',
'signer',
'beneficial_owner',
],
'id_type' => 'nik',
'id_number' => '3171015505900001',
'ownership_pct' => 60,
],
],
'bank' => [
'bank_name' => 'BCA',
'bank_account_holder' => 'PT Sinar Laut Logistik',
'account_number' => '1234567890',
],
]),
]);
$response = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
curl_close($ch);Example response
200 OK
{
"success": true,
"data": {
"view": "full",
"subject": {
"type": "account",
"id": "8fc61a2b-dbf6-4070-a570-aaa2a6560d02",
"name": "Sinar Laut MV6XWDGH",
"kyb_status": "not_started",
"is_sandbox": true,
"account_manager": null
},
"application": {
"id": "09c9e6db-ddb2-48b0-ad86-4122b24ebb8c",
"version": 1,
"status": "draft",
"entity_type": "pt",
"legal_name": "PT Sinar Laut Logistik MV6XWDGH",
"country_code": "ID",
"nib": "9120911597635",
"npwp": "012345678901000",
"address": "Jl. Pelabuhan Raya 12, Tanjung Priok, Jakarta Utara 14310",
"industry": "Logistics",
"monthly_volume": "1b_10b",
"website": "https://sinarlaut.example",
"bank": {
"bank_name": "BCA",
"bank_account_holder": "PT Sinar Laut Logistik MV6XWDGH",
"account_number_last4": "2219",
"has_account_number": true
},
"people": [
{
"id": "e5bea0ba-c7ae-4387-bf5f-862d6c50fafa",
"full_name": "Ayu Pratiwi",
"position": "President Director",
"roles": [
"director",
"signer"
],
"id_type": "nik",
"id_number_last4": "0001",
"has_id_number": true,
"nationality": "ID",
"ownership_pct": 60
},
{
"id": "99c7a474-8ca2-497b-acb6-3121a17041c7",
"full_name": "Bima Santoso",
"position": "Director",
"roles": [
"director",
"beneficial_owner"
],
"id_type": "nik",
"id_number_last4": "0002",
"has_id_number": true,
"nationality": "ID",
"ownership_pct": 40
}
],
"documents": [],
"change_requests": [],
"submitted_at": null,
"decided_at": null,
"decision_reason": null,
"risk_tier_assigned": null,
"updated_at": "2026-10-13T17:13:17.664Z"
},
"progress": {
"steps": {
"company": true,
"people": true,
"bank": true,
"documents": false
},
"done": 3,
"total": 4,
"missing_documents": [
{
"key": "akta_pendirian",
"type": "akta_pendirian",
"label": "Akta pendirian",
"hint": "The deed of establishment.",
"required": true,
"person_id": null
},
{
"key": "sk_kemenkumham",
"type": "sk_kemenkumham",
"label": "SK Kemenkumham",
"hint": "The Ministry of Law approval for the deed.",
"required": true,
"person_id": null
}
],
"complete": false
},
"slots": [
{
"key": "akta_pendirian",
"type": "akta_pendirian",
"label": "Akta pendirian",
"hint": "The deed of establishment.",
"required": true,
"person_id": null
},
{
"key": "akta_perubahan",
"type": "akta_perubahan",
"label": "Latest akta perubahan",
"hint": "The most recent amendment deed, if the company has one.",
"required": false,
"person_id": null
}
],
"history": [
{
"version": 1,
"status": "draft",
"submitted_at": null,
"decided_at": null,
"decision_reason": null
}
],
"review": {
"min_days": 1,
"max_days": 3,
"due_by": null
},
"can_edit": true
}
}Lists are cut to the first 2 items here.
Errors
| Status | Code | Cause |
|---|---|---|
400 | INVALID_BODY | A field has the wrong format, see error.details |
403 | FORBIDDEN | Your role cannot edit the application |
403 | KEY_NOT_ALLOWED | Called with an API key instead of a dashboard session |
409 | APPLICATION_LOCKED | The application is submitted and waiting for review |
409 | ALREADY_VERIFIED | The workspace is already verified |
409 | CONCURRENT_MODIFICATION | The application changed while saving. Reload and try again |
400 | INVALID_ACCOUNT | X-Enterprise-Account is not a UUID |
403 | NO_WORKSPACE | You are not an active member of that workspace |
401 | UNAUTHORIZED, SESSION_EXPIRED, INVALID_TOKEN | No session or key, the session expired, or the key is wrong or revoked |
403 | CSRF_FAILED | Session call without the matching X-CSRF-Token header |
429 | RATE_LIMITED | Over the rate limit. Wait for Retry-After seconds |
Upload a document file
Uploads one file and returns an upload_id to attach with Attach a document. Send multipart/form-data with one part named file.
Accepted: PDF, JPG, PNG and WebP up to 10 MB. The type is read from the file content and must match both the file name and the part's Content-Type. Images are capped at 8000 pixels a side and re-encoded, which removes EXIF and location data. PDFs with a password, scripts, automatic actions or attachments are refused. Files are private: they are never served from a public URL.
- Auth: Dashboard session with
X-CSRF-Tokenon writes. API keys get403 KEY_NOT_ALLOWED: KYB is filled in by people, not integrations. - Role:
adminorowner. - Rate limit: 30 a minute per user and 60 a minute per IP address for this route, plus the shared 300 a minute. At most 60 KYB uploads a day per user.
- Idempotency: Not supported. A retry stores another file; unattached uploads are deleted after a day.
Body (multipart)
| Name | Type | Required | Description |
|---|---|---|---|
file | file | Yes | The document, PDF, JPG, PNG or WebP, at most 10 MB |
Example request
curl -X POST https://api.rekberpay.com/v1/enterprise/kyb/uploads \
-b cookies.txt \
-H "X-CSRF-Token: $CSRF_TOKEN" \
-H "X-Enterprise-Account: $WORKSPACE_ID" \
-F "[email protected];type=application/pdf"import { openAsBlob } from 'node:fs';
const form = new FormData();
form.append('file', await openAsBlob('nib.pdf', { type: 'application/pdf' }), 'nib.pdf');
const res = await fetch('https://api.rekberpay.com/v1/enterprise/kyb/uploads', {
method: 'POST',
headers: {
Cookie: process.env.REKBERPAY_SESSION_COOKIE, // the session cookie from POST /v1/auth/login
'X-CSRF-Token': process.env.REKBERPAY_CSRF_TOKEN,
'X-Enterprise-Account': process.env.WORKSPACE_ID,
},
body: form,
});
const data = await res.json();import os
import requests
session = requests.Session()
# The session cookie from POST /v1/auth/login
session.cookies.set("__Host-rekber_session", os.environ["REKBERPAY_SESSION"], domain="api.rekberpay.com")
with open("nib.pdf", "rb") as fh:
resp = session.post(
"https://api.rekberpay.com/v1/enterprise/kyb/uploads",
headers={
"X-CSRF-Token": os.environ["REKBERPAY_CSRF_TOKEN"],
"X-Enterprise-Account": os.environ["WORKSPACE_ID"],
},
files={"file": ("nib.pdf", fh, "application/pdf")},
timeout=60,
)
print(resp.status_code, resp.json())<?php
$ch = curl_init('https://api.rekberpay.com/v1/enterprise/kyb/uploads');
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_COOKIEJAR => 'cookies.txt',
CURLOPT_COOKIEFILE => 'cookies.txt',
CURLOPT_HTTPHEADER => [
'X-CSRF-Token: ' . getenv('REKBERPAY_CSRF_TOKEN'),
'X-Enterprise-Account: ' . getenv('WORKSPACE_ID'),
],
CURLOPT_POSTFIELDS => ['file' => new CURLFile('nib.pdf', 'application/pdf', 'nib.pdf')],
]);
$response = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
curl_close($ch);Example response
201 Created
{
"success": true,
"data": {
"upload_id": "626500a8-25d8-41d6-b056-c533027fc736/1791911597675_98c93f7dbe3bc789d2226f40751b138a.pdf",
"mime": "application/pdf",
"size_bytes": 166,
"sha256": "ada1c8a6c1878e7232df92a86849e8136f78db3ea28124d9c1c6b3cdc941b5ce"
}
}Errors
| Status | Code | Cause |
|---|---|---|
400 | NO_FILE | No single part named file |
400 | TYPE_MISMATCH | The content, file name and Content-Type do not agree |
400 | TYPE_NOT_ALLOWED | Not a PDF, JPG, PNG or WebP file (SVG, HTML, archives and Office files are refused) |
400 | POLYGLOT | The file hides another format or markup |
400 | PDF_ENCRYPTED | The PDF is password protected |
400 | PDF_ACTIVE_CONTENT, PDF_EMBEDDED_FILE | The PDF has scripts, actions or attached files |
400 | IMAGE_TOO_LARGE, IMAGE_DECODE_FAILED | The image is too large or could not be decoded |
413 | FILE_TOO_LARGE | Over 10 MB |
429 | QUOTA_EXCEEDED | Over 60 KYB uploads in 24 hours |
400 | INVALID_ACCOUNT | X-Enterprise-Account is not a UUID |
403 | NO_WORKSPACE | You are not an active member of that workspace |
401 | UNAUTHORIZED, SESSION_EXPIRED, INVALID_TOKEN | No session or key, the session expired, or the key is wrong or revoked |
403 | CSRF_FAILED | Session call without the matching X-CSRF-Token header |
429 | RATE_LIMITED | Over the rate limit. Wait for Retry-After seconds |
Attach a document
Attaches one of your own unattached uploads to the draft as a document of doc_type. An upload can be attached once, only by the user who uploaded it. Person documents (ktp, passport) name the person with person_id. Uploading again for the same slot replaces the earlier document.
- Auth: Dashboard session with
X-CSRF-Tokenon writes. API keys get403 KEY_NOT_ALLOWED: KYB is filled in by people, not integrations. - Role:
adminorowner. - Rate limit: 30 a minute per user for this route, plus the shared 300 a minute. At most 40 documents per application.
- Idempotency: Not needed: attaching the same upload to the same application again changes nothing.
Body (JSON)
| Name | Type | Required | Description |
|---|---|---|---|
upload_id | string | Yes | The upload_id from Upload a document file |
doc_type | string | Yes | akta_pendirian, akta_perubahan, sk_kemenkumham, nib, npwp, ktp, passport, bank_proof, authority_letter, registration_certificate or tax_id |
person_id | UUID | For ktp and passport | A person from application.people |
Example request
curl -X POST https://api.rekberpay.com/v1/enterprise/kyb/documents \
-b cookies.txt \
-H "X-CSRF-Token: $CSRF_TOKEN" \
-H "X-Enterprise-Account: $WORKSPACE_ID" \
-H "Content-Type: application/json" \
-d '{
"upload_id": "0b6c8a52-1f0e-4d4a-9a55-6e8c2f2b1c11/1760367000123_4f9c2d7e8a1b3c5d6e7f8091a2b3c4d5.pdf",
"doc_type": "nib"
}'const res = await fetch('https://api.rekberpay.com/v1/enterprise/kyb/documents', {
method: 'POST',
headers: {
Cookie: process.env.REKBERPAY_SESSION_COOKIE, // the session cookie from POST /v1/auth/login
'X-CSRF-Token': process.env.REKBERPAY_CSRF_TOKEN,
'X-Enterprise-Account': process.env.WORKSPACE_ID,
'Content-Type': 'application/json',
},
body: JSON.stringify({
"upload_id": "0b6c8a52-1f0e-4d4a-9a55-6e8c2f2b1c11/1760367000123_4f9c2d7e8a1b3c5d6e7f8091a2b3c4d5.pdf",
"doc_type": "nib"
}),
});
const data = await res.json();import os
import requests
session = requests.Session()
# The session cookie from POST /v1/auth/login
session.cookies.set("__Host-rekber_session", os.environ["REKBERPAY_SESSION"], domain="api.rekberpay.com")
resp = session.post(
"https://api.rekberpay.com/v1/enterprise/kyb/documents",
headers={
"X-CSRF-Token": os.environ["REKBERPAY_CSRF_TOKEN"],
"X-Enterprise-Account": os.environ["WORKSPACE_ID"],
},
json={
"upload_id": "0b6c8a52-1f0e-4d4a-9a55-6e8c2f2b1c11/1760367000123_4f9c2d7e8a1b3c5d6e7f8091a2b3c4d5.pdf",
"doc_type": "nib"
},
timeout=30,
)
print(resp.status_code, resp.json())<?php
$ch = curl_init('https://api.rekberpay.com/v1/enterprise/kyb/documents');
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_COOKIEJAR => 'cookies.txt',
CURLOPT_COOKIEFILE => 'cookies.txt',
CURLOPT_HTTPHEADER => [
'X-CSRF-Token: ' . getenv('REKBERPAY_CSRF_TOKEN'),
'X-Enterprise-Account: ' . getenv('WORKSPACE_ID'),
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => json_encode([
'upload_id' => '0b6c8a52-1f0e-4d4a-9a55-6e8c2f2b1c11/1760367000123_4f9c2d7e8a1b3c5d6e7f8091a2b3c4d5.pdf',
'doc_type' => 'nib',
]),
]);
$response = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
curl_close($ch);Example response
201 Created
{
"success": true,
"data": {
"view": "full",
"subject": {
"type": "account",
"id": "8fc61a2b-dbf6-4070-a570-aaa2a6560d02",
"name": "Sinar Laut MV6XWDGH",
"kyb_status": "not_started",
"is_sandbox": true,
"account_manager": null
},
"application": {
"id": "09c9e6db-ddb2-48b0-ad86-4122b24ebb8c",
"version": 1,
"status": "draft",
"entity_type": "pt",
"legal_name": "PT Sinar Laut Logistik MV6XWDGH",
"country_code": "ID",
"nib": "9120911597635",
"npwp": "012345678901000",
"address": "Jl. Pelabuhan Raya 12, Tanjung Priok, Jakarta Utara 14310",
"industry": "Logistics",
"monthly_volume": "1b_10b",
"website": "https://sinarlaut.example",
"bank": {
"bank_name": "BCA",
"bank_account_holder": "PT Sinar Laut Logistik MV6XWDGH",
"account_number_last4": "2219",
"has_account_number": true
},
"people": [
{
"id": "e5bea0ba-c7ae-4387-bf5f-862d6c50fafa",
"full_name": "Ayu Pratiwi",
"position": "President Director",
"roles": [
"director",
"signer"
],
"id_type": "nik",
"id_number_last4": "0001",
"has_id_number": true,
"nationality": "ID",
"ownership_pct": 60
},
{
"id": "99c7a474-8ca2-497b-acb6-3121a17041c7",
"full_name": "Bima Santoso",
"position": "Director",
"roles": [
"director",
"beneficial_owner"
],
"id_type": "nik",
"id_number_last4": "0002",
"has_id_number": true,
"nationality": "ID",
"ownership_pct": 40
}
],
"documents": [
{
"id": "47dc9b99-6a6d-4f12-9d4d-a6435ff4506d",
"doc_type": "akta_pendirian",
"person_id": null,
"original_name": "akta_pendirian.pdf",
"mime": "application/pdf",
"size_bytes": 166,
"status": "uploaded",
"reviewer_note": null,
"created_at": "2026-10-13T17:13:17.683Z"
}
],
"change_requests": [],
"submitted_at": null,
"decided_at": null,
"decision_reason": null,
"risk_tier_assigned": null,
"updated_at": "2026-10-13T17:13:17.683Z"
},
"progress": {
"steps": {
"company": true,
"people": true,
"bank": true,
"documents": false
},
"done": 3,
"total": 4,
"missing_documents": [
{
"key": "sk_kemenkumham",
"type": "sk_kemenkumham",
"label": "SK Kemenkumham",
"hint": "The Ministry of Law approval for the deed.",
"required": true,
"person_id": null
},
{
"key": "nib",
"type": "nib",
"label": "NIB (OSS)",
"hint": "Your business registration number from OSS.",
"required": true,
"person_id": null
}
],
"complete": false
},
"slots": [
{
"key": "akta_pendirian",
"type": "akta_pendirian",
"label": "Akta pendirian",
"hint": "The deed of establishment.",
"required": true,
"person_id": null
},
{
"key": "akta_perubahan",
"type": "akta_perubahan",
"label": "Latest akta perubahan",
"hint": "The most recent amendment deed, if the company has one.",
"required": false,
"person_id": null
}
],
"history": [
{
"version": 1,
"status": "draft",
"submitted_at": null,
"decided_at": null,
"decision_reason": null
}
],
"review": {
"min_days": 1,
"max_days": 3,
"due_by": null
},
"can_edit": true
}
}Lists are cut to the first 2 items here.
Errors
| Status | Code | Cause |
|---|---|---|
400 | INVALID_BODY | Unknown doc_type, or a person document without person_id |
404 | UPLOAD_NOT_FOUND | No upload of yours with this ID, or it belongs to someone else |
409 | UPLOAD_ALREADY_BOUND | The upload is already attached to another application |
404 | PERSON_NOT_FOUND | person_id is not on this application |
409 | DOCUMENT_LIMIT | The application already has 40 documents |
409 | APPLICATION_LOCKED | The application is submitted and waiting for review |
400 | INVALID_ACCOUNT | X-Enterprise-Account is not a UUID |
403 | NO_WORKSPACE | You are not an active member of that workspace |
401 | UNAUTHORIZED, SESSION_EXPIRED, INVALID_TOKEN | No session or key, the session expired, or the key is wrong or revoked |
403 | CSRF_FAILED | Session call without the matching X-CSRF-Token header |
429 | RATE_LIMITED | Over the rate limit. Wait for Retry-After seconds |
Remove a document
Removes a document from the draft. Documents of a submitted or decided version cannot be removed.
- Auth: Dashboard session with
X-CSRF-Tokenon writes. API keys get403 KEY_NOT_ALLOWED: KYB is filled in by people, not integrations. - Role:
adminorowner. - Rate limit: 30 a minute per user for this route, plus the shared 300 a minute.
- Idempotency: Not needed. A second call answers
404.
Path parameters
| Name | Type | Description |
|---|---|---|
id | UUID | The document id |
Example request
curl -X DELETE https://api.rekberpay.com/v1/enterprise/kyb/documents/d0c00001-d0c0-4d0c-8d0c-d0c00000000a \
-b cookies.txt \
-H "X-CSRF-Token: $CSRF_TOKEN" \
-H "X-Enterprise-Account: $WORKSPACE_ID"const res = await fetch('https://api.rekberpay.com/v1/enterprise/kyb/documents/d0c00001-d0c0-4d0c-8d0c-d0c00000000a', {
method: 'DELETE',
headers: {
Cookie: process.env.REKBERPAY_SESSION_COOKIE, // the session cookie from POST /v1/auth/login
'X-CSRF-Token': process.env.REKBERPAY_CSRF_TOKEN,
'X-Enterprise-Account': process.env.WORKSPACE_ID,
},
});
const data = await res.json();import os
import requests
session = requests.Session()
# The session cookie from POST /v1/auth/login
session.cookies.set("__Host-rekber_session", os.environ["REKBERPAY_SESSION"], domain="api.rekberpay.com")
resp = session.delete(
"https://api.rekberpay.com/v1/enterprise/kyb/documents/d0c00001-d0c0-4d0c-8d0c-d0c00000000a",
headers={
"X-CSRF-Token": os.environ["REKBERPAY_CSRF_TOKEN"],
"X-Enterprise-Account": os.environ["WORKSPACE_ID"],
},
timeout=30,
)
print(resp.status_code, resp.json())<?php
$ch = curl_init('https://api.rekberpay.com/v1/enterprise/kyb/documents/d0c00001-d0c0-4d0c-8d0c-d0c00000000a');
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'DELETE',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_COOKIEJAR => 'cookies.txt',
CURLOPT_COOKIEFILE => 'cookies.txt',
CURLOPT_HTTPHEADER => [
'X-CSRF-Token: ' . getenv('REKBERPAY_CSRF_TOKEN'),
'X-Enterprise-Account: ' . getenv('WORKSPACE_ID'),
],
]);
$response = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
curl_close($ch);Example response
200 OK
{
"success": true,
"data": {
"view": "full",
"subject": {
"type": "account",
"id": "8fc61a2b-dbf6-4070-a570-aaa2a6560d02",
"name": "Sinar Laut MV6XWDGH",
"kyb_status": "draft",
"is_sandbox": true,
"account_manager": null
},
"application": {
"id": "61b95d77-3a11-4d25-ba85-27437db152ea",
"version": 2,
"status": "draft",
"entity_type": "pt",
"legal_name": "PT Sinar Laut Logistik MV6XWDGH",
"country_code": "ID",
"nib": "9120911597635",
"npwp": "012345678901000",
"address": "Jl. Pelabuhan Raya 12, Tanjung Priok, Jakarta Utara 14310",
"industry": "Logistics",
"monthly_volume": "1b_10b",
"website": "https://sinarlaut.example",
"bank": {
"bank_name": "BCA",
"bank_account_holder": "PT Sinar Laut Logistik MV6XWDGH",
"account_number_last4": "2219",
"has_account_number": true
},
"people": [
{
"id": "7d0392ef-c14d-4836-9ea6-2746d6f98486",
"full_name": "Ayu Pratiwi",
"position": "President Director",
"roles": [
"director",
"signer"
],
"id_type": "nik",
"id_number_last4": "0001",
"has_id_number": true,
"nationality": "ID",
"ownership_pct": 60
},
{
"id": "80e13305-314e-49c7-8068-c5666d60177e",
"full_name": "Bima Santoso",
"position": "Director",
"roles": [
"director",
"beneficial_owner"
],
"id_type": "nik",
"id_number_last4": "0002",
"has_id_number": true,
"nationality": "ID",
"ownership_pct": 40
}
],
"documents": [
{
"id": "c4682fbc-deb9-4978-8cc9-fc05b9d8e963",
"doc_type": "akta_pendirian",
"person_id": null,
"original_name": "akta_pendirian.pdf",
"mime": "application/pdf",
"size_bytes": 166,
"status": "uploaded",
"reviewer_note": null,
"created_at": "2026-10-13T17:13:17.843Z"
},
{
"id": "10a0d3f0-225e-48a8-bcf4-89abfbe14d9b",
"doc_type": "bank_proof",
"person_id": null,
"original_name": "bank_proof.pdf",
"mime": "application/pdf",
"size_bytes": 162,
"status": "uploaded",
"reviewer_note": null,
"created_at": "2026-10-13T17:13:17.843Z"
}
],
"change_requests": [
{
"item": "doc:npwp",
"label": "NPWP badan",
"reason": "The NPWP photo is blurred."
}
],
"submitted_at": null,
"decided_at": null,
"decision_reason": null,
"risk_tier_assigned": null,
"updated_at": "2026-10-13T17:13:17.856Z"
},
"progress": {
"steps": {
"company": true,
"people": true,
"bank": true,
"documents": false
},
"done": 3,
"total": 4,
"missing_documents": [
{
"key": "npwp",
"type": "npwp",
"label": "NPWP badan",
"hint": "The company tax card or certificate.",
"required": true,
"person_id": null
}
],
"complete": false
},
"slots": [
{
"key": "akta_pendirian",
"type": "akta_pendirian",
"label": "Akta pendirian",
"hint": "The deed of establishment.",
"required": true,
"person_id": null
},
{
"key": "akta_perubahan",
"type": "akta_perubahan",
"label": "Latest akta perubahan",
"hint": "The most recent amendment deed, if the company has one.",
"required": false,
"person_id": null
}
],
"history": [
{
"version": 2,
"status": "draft",
"submitted_at": null,
"decided_at": null,
"decision_reason": null
},
{
"version": 1,
"status": "changes_requested",
"submitted_at": "2026-10-13T17:13:17.801Z",
"decided_at": "2026-10-13T17:13:17.841Z",
"decision_reason": null
}
],
"review": {
"min_days": 1,
"max_days": 3,
"due_by": null
},
"can_edit": true
}
}Lists are cut to the first 2 items here.
Errors
| Status | Code | Cause |
|---|---|---|
404 | NOT_FOUND | No document with this ID on the current draft |
409 | APPLICATION_LOCKED | The application is submitted and waiting for review |
400 | INVALID_ACCOUNT | X-Enterprise-Account is not a UUID |
403 | NO_WORKSPACE | You are not an active member of that workspace |
401 | UNAUTHORIZED, SESSION_EXPIRED, INVALID_TOKEN | No session or key, the session expired, or the key is wrong or revoked |
403 | CSRF_FAILED | Session call without the matching X-CSRF-Token header |
429 | RATE_LIMITED | Over the rate limit. Wait for Retry-After seconds |
Get a document link
A link to view one document, valid for 5 minutes and only for you: it is signed for your user ID, so open it with the same session. The file is re-checked against its SHA-256 on every read and sent with Cache-Control: private, no-store; PDFs download as attachments.
- Auth: Dashboard session with
X-CSRF-Tokenon writes. API keys get403 KEY_NOT_ALLOWED: KYB is filled in by people, not integrations. - Role:
finance,adminorowner. - Rate limit: 120 a minute per user for this route, plus the shared 300 a minute.
- Idempotency: Not needed. Reading has no side effects.
Path parameters
| Name | Type | Description |
|---|---|---|
id | UUID | The document id |
Example request
curl https://api.rekberpay.com/v1/enterprise/kyb/documents/d0c00001-d0c0-4d0c-8d0c-d0c00000000a/url \
-b cookies.txt \
-H "X-Enterprise-Account: $WORKSPACE_ID"const res = await fetch('https://api.rekberpay.com/v1/enterprise/kyb/documents/d0c00001-d0c0-4d0c-8d0c-d0c00000000a/url', {
headers: {
Cookie: process.env.REKBERPAY_SESSION_COOKIE, // the session cookie from POST /v1/auth/login
'X-Enterprise-Account': process.env.WORKSPACE_ID,
},
});
const data = await res.json();import os
import requests
session = requests.Session()
# The session cookie from POST /v1/auth/login
session.cookies.set("__Host-rekber_session", os.environ["REKBERPAY_SESSION"], domain="api.rekberpay.com")
resp = session.get(
"https://api.rekberpay.com/v1/enterprise/kyb/documents/d0c00001-d0c0-4d0c-8d0c-d0c00000000a/url",
headers={
"X-Enterprise-Account": os.environ["WORKSPACE_ID"],
},
timeout=30,
)
print(resp.status_code, resp.json())<?php
$ch = curl_init('https://api.rekberpay.com/v1/enterprise/kyb/documents/d0c00001-d0c0-4d0c-8d0c-d0c00000000a/url');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_COOKIEJAR => 'cookies.txt',
CURLOPT_COOKIEFILE => 'cookies.txt',
CURLOPT_HTTPHEADER => [
'X-Enterprise-Account: ' . getenv('WORKSPACE_ID'),
],
]);
$response = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
curl_close($ch);Example response
200 OK
{
"success": true,
"data": {
"url": "/v1/kyb/files/86552ee4-5106-4524-9885-16bfd9ab7d80?exp=1791911897&sig=bTOf-ZzSc_9z_gB3y0d7WntW_0sRbPX8fn6xOdNd4ZA",
"expires_at": "2026-10-13T17:18:17.000Z"
}
}Errors
| Status | Code | Cause |
|---|---|---|
404 | NOT_FOUND | No document with this ID on this workspace's applications |
400 | INVALID_ACCOUNT | X-Enterprise-Account is not a UUID |
403 | NO_WORKSPACE | You are not an active member of that workspace |
401 | UNAUTHORIZED, SESSION_EXPIRED, INVALID_TOKEN | No session or key, the session expired, or the key is wrong or revoked |
403 | CSRF_FAILED | Session call without the matching X-CSRF-Token header |
429 | RATE_LIMITED | Over the rate limit. Wait for Retry-After seconds |
Submit for review
Sends the draft to RekberPay for review. Every step must be complete: the company details, at least one director and one authorised signer with identity numbers, the bank account and every required document. The automatic checks run now (formats, duplicate NIB, NPWP or bank account on another workspace, screening, document content); they are shown to the reviewer and never reject on their own.
A verified workspace can update its details: the first save opens a new version and the workspace stays verified while RekberPay reviews the change. A suspended workspace cannot submit (409 SUSPENDED).
The application moves to submitted and the workspace's kyb_status to pending. We review in 1 to 3 business days; review.due_by is the end of that window.
- Auth: Dashboard session with
X-CSRF-Tokenon writes. API keys get403 KEY_NOT_ALLOWED: KYB is filled in by people, not integrations. - Role:
adminorowner. - Rate limit: 10 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 returns the first answer withIdempotent-Replay: true.
Example request
curl -X POST https://api.rekberpay.com/v1/enterprise/kyb/submit \
-b cookies.txt \
-H "X-CSRF-Token: $CSRF_TOKEN" \
-H "X-Enterprise-Account: $WORKSPACE_ID" \
-H "Idempotency-Key: 99999995-9999-4999-8999-999999999999" \
-H "Content-Type: application/json" \
-d '{}'const res = await fetch('https://api.rekberpay.com/v1/enterprise/kyb/submit', {
method: 'POST',
headers: {
Cookie: process.env.REKBERPAY_SESSION_COOKIE, // the session cookie from POST /v1/auth/login
'X-CSRF-Token': process.env.REKBERPAY_CSRF_TOKEN,
'X-Enterprise-Account': process.env.WORKSPACE_ID,
'Idempotency-Key': '99999995-9999-4999-8999-999999999999',
'Content-Type': 'application/json',
},
body: JSON.stringify({}),
});
const data = await res.json();import os
import requests
session = requests.Session()
# The session cookie from POST /v1/auth/login
session.cookies.set("__Host-rekber_session", os.environ["REKBERPAY_SESSION"], domain="api.rekberpay.com")
resp = session.post(
"https://api.rekberpay.com/v1/enterprise/kyb/submit",
headers={
"X-CSRF-Token": os.environ["REKBERPAY_CSRF_TOKEN"],
"X-Enterprise-Account": os.environ["WORKSPACE_ID"],
"Idempotency-Key": "99999995-9999-4999-8999-999999999999",
},
json={},
timeout=30,
)
print(resp.status_code, resp.json())<?php
$ch = curl_init('https://api.rekberpay.com/v1/enterprise/kyb/submit');
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_COOKIEJAR => 'cookies.txt',
CURLOPT_COOKIEFILE => 'cookies.txt',
CURLOPT_HTTPHEADER => [
'X-CSRF-Token: ' . getenv('REKBERPAY_CSRF_TOKEN'),
'X-Enterprise-Account: ' . getenv('WORKSPACE_ID'),
'Idempotency-Key: 99999995-9999-4999-8999-999999999999',
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => json_encode(new stdClass()),
]);
$response = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
curl_close($ch);Example response
200 OK
{
"success": true,
"data": {
"view": "full",
"subject": {
"type": "account",
"id": "8fc61a2b-dbf6-4070-a570-aaa2a6560d02",
"name": "Sinar Laut MV6XWDGH",
"kyb_status": "pending",
"is_sandbox": true,
"account_manager": null
},
"application": {
"id": "09c9e6db-ddb2-48b0-ad86-4122b24ebb8c",
"version": 1,
"status": "submitted",
"entity_type": "pt",
"legal_name": "PT Sinar Laut Logistik MV6XWDGH",
"country_code": "ID",
"nib": "9120911597635",
"npwp": "012345678901000",
"address": "Jl. Pelabuhan Raya 12, Tanjung Priok, Jakarta Utara 14310",
"industry": "Logistics",
"monthly_volume": "1b_10b",
"website": "https://sinarlaut.example",
"bank": {
"bank_name": "BCA",
"bank_account_holder": "PT Sinar Laut Logistik MV6XWDGH",
"account_number_last4": "2219",
"has_account_number": true
},
"people": [
{
"id": "e5bea0ba-c7ae-4387-bf5f-862d6c50fafa",
"full_name": "Ayu Pratiwi",
"position": "President Director",
"roles": [
"director",
"signer"
],
"id_type": "nik",
"id_number_last4": "0001",
"has_id_number": true,
"nationality": "ID",
"ownership_pct": 60
},
{
"id": "99c7a474-8ca2-497b-acb6-3121a17041c7",
"full_name": "Bima Santoso",
"position": "Director",
"roles": [
"director",
"beneficial_owner"
],
"id_type": "nik",
"id_number_last4": "0002",
"has_id_number": true,
"nationality": "ID",
"ownership_pct": 40
}
],
"documents": [
{
"id": "47dc9b99-6a6d-4f12-9d4d-a6435ff4506d",
"doc_type": "akta_pendirian",
"person_id": null,
"original_name": "akta_pendirian.pdf",
"mime": "application/pdf",
"size_bytes": 166,
"status": "uploaded",
"reviewer_note": null,
"created_at": "2026-10-13T17:13:17.683Z"
},
{
"id": "da546100-4ae6-4f27-ad36-e8f796fc7e8e",
"doc_type": "bank_proof",
"person_id": null,
"original_name": "bank_proof.pdf",
"mime": "application/pdf",
"size_bytes": 162,
"status": "uploaded",
"reviewer_note": null,
"created_at": "2026-10-13T17:13:17.730Z"
}
],
"change_requests": [],
"submitted_at": "2026-10-13T17:13:17.801Z",
"decided_at": null,
"decision_reason": null,
"risk_tier_assigned": null,
"updated_at": "2026-10-13T17:13:17.801Z"
},
"progress": {
"steps": {
"company": true,
"people": true,
"bank": true,
"documents": true
},
"done": 4,
"total": 4,
"missing_documents": [],
"complete": true
},
"slots": [
{
"key": "akta_pendirian",
"type": "akta_pendirian",
"label": "Akta pendirian",
"hint": "The deed of establishment.",
"required": true,
"person_id": null
},
{
"key": "akta_perubahan",
"type": "akta_perubahan",
"label": "Latest akta perubahan",
"hint": "The most recent amendment deed, if the company has one.",
"required": false,
"person_id": null
}
],
"history": [
{
"version": 1,
"status": "submitted",
"submitted_at": "2026-10-13T17:13:17.801Z",
"decided_at": null,
"decision_reason": null
}
],
"review": {
"min_days": 1,
"max_days": 3,
"due_by": "2026-10-18T17:13:17.801Z"
},
"can_edit": false
}
}Lists are cut to the first 2 items here.
Errors
| Status | Code | Cause |
|---|---|---|
422 | INCOMPLETE | A step is not complete. error.details lists the missing fields and documents |
409 | APPLICATION_LOCKED | Already submitted |
409 | SUSPENDED | The workspace is suspended. Contact RekberPay |
409 | CONCURRENT_MODIFICATION | The application changed while submitting. Reload and try again |
409 | IDEMPOTENCY_KEY_IN_FLIGHT | The first call with this key is still running |
422 | IDEMPOTENCY_KEY_CONFLICT | The key was used with another body |
400 | INVALID_ACCOUNT | X-Enterprise-Account is not a UUID |
403 | NO_WORKSPACE | You are not an active member of that workspace |
401 | UNAUTHORIZED, SESSION_EXPIRED, INVALID_TOKEN | No session or key, the session expired, or the key is wrong or revoked |
403 | CSRF_FAILED | Session call without the matching X-CSRF-Token header |
429 | RATE_LIMITED | Over the rate limit. Wait for Retry-After seconds |
Send a counterparty verification link
Creates a signed link that lets the counterparty verify its company with the same four steps. The link is valid for 14 days. The first signed-in RekberPay user who opens it becomes its only user. RekberPay reviews it in the same queue, and the decision sets the counterparty's kyb_status. Creating a new link revokes the earlier ones for that counterparty. Send the url to your contact yourself.
- Auth: Dashboard session with
X-CSRF-Tokenon writes. API keys get403 KEY_NOT_ALLOWED: KYB is filled in by people, not integrations. - Role:
adminorowner. - Rate limit: 10 a minute per user for this route, plus the shared 300 a minute.
- Idempotency: Not supported, so the link never sits in a replay cache. A retry creates a new link and revokes the earlier one.
Path parameters
| Name | Type | Description |
|---|---|---|
id | UUID | The counterparty id |
Example request
curl -X POST https://api.rekberpay.com/v1/enterprise/counterparties/cccccccc-cccc-4ccc-8ccc-cccccccccccc/kyb-invite \
-b cookies.txt \
-H "X-CSRF-Token: $CSRF_TOKEN" \
-H "X-Enterprise-Account: $WORKSPACE_ID" \
-H "Content-Type: application/json" \
-d '{}'const res = await fetch('https://api.rekberpay.com/v1/enterprise/counterparties/cccccccc-cccc-4ccc-8ccc-cccccccccccc/kyb-invite', {
method: 'POST',
headers: {
Cookie: process.env.REKBERPAY_SESSION_COOKIE, // the session cookie from POST /v1/auth/login
'X-CSRF-Token': process.env.REKBERPAY_CSRF_TOKEN,
'X-Enterprise-Account': process.env.WORKSPACE_ID,
'Content-Type': 'application/json',
},
body: JSON.stringify({}),
});
const data = await res.json();import os
import requests
session = requests.Session()
# The session cookie from POST /v1/auth/login
session.cookies.set("__Host-rekber_session", os.environ["REKBERPAY_SESSION"], domain="api.rekberpay.com")
resp = session.post(
"https://api.rekberpay.com/v1/enterprise/counterparties/cccccccc-cccc-4ccc-8ccc-cccccccccccc/kyb-invite",
headers={
"X-CSRF-Token": os.environ["REKBERPAY_CSRF_TOKEN"],
"X-Enterprise-Account": os.environ["WORKSPACE_ID"],
},
json={},
timeout=30,
)
print(resp.status_code, resp.json())<?php
$ch = curl_init('https://api.rekberpay.com/v1/enterprise/counterparties/cccccccc-cccc-4ccc-8ccc-cccccccccccc/kyb-invite');
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_COOKIEJAR => 'cookies.txt',
CURLOPT_COOKIEFILE => 'cookies.txt',
CURLOPT_HTTPHEADER => [
'X-CSRF-Token: ' . getenv('REKBERPAY_CSRF_TOKEN'),
'X-Enterprise-Account: ' . getenv('WORKSPACE_ID'),
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => json_encode(new stdClass()),
]);
$response = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
curl_close($ch);Example response
201 Created
{
"success": true,
"data": {
"url": "https://rekberpay.com/enterprise/verify/65732574-5893-47cc-aade-00e416d1b649.1793121197.30B15AStFrVbMj3HGht18kaOPZ8_P_cCA47q7NWnHVI",
"token": "65732574-5893-47cc-aade-00e416d1b649.1793121197.30B15AStFrVbMj3HGht18kaOPZ8_P_cCA47q7NWnHVI",
"expires_at": "2026-10-27T17:13:17.000Z"
}
}Errors
| Status | Code | Cause |
|---|---|---|
404 | NOT_FOUND | No counterparty with this ID in the workspace |
409 | ALREADY_VERIFIED | The counterparty is already verified |
400 | INVALID_ACCOUNT | X-Enterprise-Account is not a UUID |
403 | NO_WORKSPACE | You are not an active member of that workspace |
401 | UNAUTHORIZED, SESSION_EXPIRED, INVALID_TOKEN | No session or key, the session expired, or the key is wrong or revoked |
403 | CSRF_FAILED | Session call without the matching X-CSRF-Token header |
429 | RATE_LIMITED | Over the rate limit. Wait for Retry-After seconds |
Open a verification link
The counterparty side. Returns the same full view as Get the KYB application, for the counterparty's application. The first signed-in user to open a link claims it; anyone else gets 403 INVITE_CLAIMED. No workspace is needed.
- Auth: Dashboard session of any signed-in RekberPay user. API keys get
403 KEY_NOT_ALLOWED. - Rate limit: 120 a minute per user for this route, plus the shared 300 a minute.
- Idempotency: Not needed. Opening twice is the same as once.
Path parameters
| Name | Type | Description |
|---|---|---|
token | string | The token at the end of the link |
Example request
curl https://api.rekberpay.com/v1/kyb/invite/$KYB_INVITE_TOKEN \
-b cookies.txtconst res = await fetch(`https://api.rekberpay.com/v1/kyb/invite/${process.env.KYB_INVITE_TOKEN}`, {
headers: {
Cookie: process.env.REKBERPAY_SESSION_COOKIE, // the session cookie from POST /v1/auth/login
},
});
const data = await res.json();import os
import requests
session = requests.Session()
# The session cookie from POST /v1/auth/login
session.cookies.set("__Host-rekber_session", os.environ["REKBERPAY_SESSION"], domain="api.rekberpay.com")
resp = session.get(
f"https://api.rekberpay.com/v1/kyb/invite/{os.environ['KYB_INVITE_TOKEN']}",
timeout=30,
)
print(resp.status_code, resp.json())<?php
$ch = curl_init('https://api.rekberpay.com/v1/kyb/invite/' . getenv('KYB_INVITE_TOKEN'));
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_COOKIEJAR => 'cookies.txt',
CURLOPT_COOKIEFILE => 'cookies.txt',
]);
$response = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
curl_close($ch);Example response
200 OK
{
"success": true,
"data": {
"view": "full",
"subject": {
"type": "counterparty",
"id": "6088d36c-5793-4f82-a024-34f064971d12",
"name": "PT Mitra Kargo MV6XWDGH",
"kyb_status": "not_started",
"invited_by": "Sinar Laut MV6XWDGH"
},
"application": {
"id": "78d3f2d7-1eed-4434-a86a-8be3941965fe",
"version": 1,
"status": "draft",
"entity_type": null,
"legal_name": "PT Mitra Kargo MV6XWDGH",
"country_code": "ID",
"nib": null,
"npwp": null,
"address": null,
"industry": null,
"monthly_volume": null,
"website": null,
"bank": {
"bank_name": null,
"bank_account_holder": null,
"account_number_last4": null,
"has_account_number": false
},
"people": [],
"documents": [],
"change_requests": [],
"submitted_at": null,
"decided_at": null,
"decision_reason": null,
"risk_tier_assigned": null,
"updated_at": "2026-10-13T17:13:17.950Z"
},
"progress": {
"steps": {
"company": false,
"people": false,
"bank": false,
"documents": false
},
"done": 0,
"total": 4,
"missing_documents": [],
"complete": false
},
"slots": [],
"history": [
{
"version": 1,
"status": "draft",
"submitted_at": null,
"decided_at": null,
"decision_reason": null
}
],
"review": {
"min_days": 1,
"max_days": 3,
"due_by": null
},
"can_edit": true
}
}Errors
| Status | Code | Cause |
|---|---|---|
404 | INVITE_INVALID | The token is malformed, forged or revoked |
410 | INVITE_EXPIRED | The link is older than 14 days. Ask for a new one |
403 | INVITE_CLAIMED | Another user already opened this link |
401 | UNAUTHORIZED, SESSION_EXPIRED, INVALID_TOKEN | No session or key, the session expired, or the key is wrong or revoked |
429 | RATE_LIMITED | Over the rate limit. Wait for Retry-After seconds |
Save a counterparty draft
The same body and rules as Save the draft, for the counterparty's application.
- Auth: Dashboard session of the user who opened the link first.
- Rate limit: 120 a minute per user for this route, plus the shared 300 a minute.
- Idempotency: Not needed. Saving the same body twice leaves the same draft.
Path parameters
| Name | Type | Description |
|---|---|---|
token | string | The token at the end of the link |
Example request
curl -X PATCH https://api.rekberpay.com/v1/kyb/invite/$KYB_INVITE_TOKEN \
-b cookies.txt \
-H "X-CSRF-Token: $CSRF_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"company": {
"legal_name": "PT Mitra Kargo Nusantara",
"entity_type": "pt",
"nib": "9120007654321"
}
}'const res = await fetch(`https://api.rekberpay.com/v1/kyb/invite/${process.env.KYB_INVITE_TOKEN}`, {
method: 'PATCH',
headers: {
Cookie: process.env.REKBERPAY_SESSION_COOKIE, // the session cookie from POST /v1/auth/login
'X-CSRF-Token': process.env.REKBERPAY_CSRF_TOKEN,
'Content-Type': 'application/json',
},
body: JSON.stringify({
"company": {
"legal_name": "PT Mitra Kargo Nusantara",
"entity_type": "pt",
"nib": "9120007654321"
}
}),
});
const data = await res.json();import os
import requests
session = requests.Session()
# The session cookie from POST /v1/auth/login
session.cookies.set("__Host-rekber_session", os.environ["REKBERPAY_SESSION"], domain="api.rekberpay.com")
resp = session.patch(
f"https://api.rekberpay.com/v1/kyb/invite/{os.environ['KYB_INVITE_TOKEN']}",
headers={
"X-CSRF-Token": os.environ["REKBERPAY_CSRF_TOKEN"],
},
json={
"company": {
"legal_name": "PT Mitra Kargo Nusantara",
"entity_type": "pt",
"nib": "9120007654321"
}
},
timeout=30,
)
print(resp.status_code, resp.json())<?php
$ch = curl_init('https://api.rekberpay.com/v1/kyb/invite/' . getenv('KYB_INVITE_TOKEN'));
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'PATCH',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_COOKIEJAR => 'cookies.txt',
CURLOPT_COOKIEFILE => 'cookies.txt',
CURLOPT_HTTPHEADER => [
'X-CSRF-Token: ' . getenv('REKBERPAY_CSRF_TOKEN'),
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => json_encode([
'company' => [
'legal_name' => 'PT Mitra Kargo Nusantara',
'entity_type' => 'pt',
'nib' => '9120007654321',
],
]),
]);
$response = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
curl_close($ch);Example response
200 OK
{
"success": true,
"data": {
"view": "full",
"subject": {
"type": "counterparty",
"id": "6088d36c-5793-4f82-a024-34f064971d12",
"name": "PT Mitra Kargo MV6XWDGH",
"kyb_status": "not_started",
"invited_by": "Sinar Laut MV6XWDGH"
},
"application": {
"id": "78d3f2d7-1eed-4434-a86a-8be3941965fe",
"version": 1,
"status": "draft",
"entity_type": "pt",
"legal_name": "PT Mitra Kargo MV6XWDGH",
"country_code": "ID",
"nib": "9121911597966",
"npwp": "012345678901000",
"address": "Jl. Pelabuhan Raya 12, Tanjung Priok, Jakarta Utara 14310",
"industry": "Logistics",
"monthly_volume": "1b_10b",
"website": "https://sinarlaut.example",
"bank": {
"bank_name": "BCA",
"bank_account_holder": "PT Mitra Kargo MV6XWDGH",
"account_number_last4": "2219",
"has_account_number": true
},
"people": [
{
"id": "c5e85da2-a7c2-4f92-b910-e212afa1e1a0",
"full_name": "Ayu Pratiwi",
"position": "President Director",
"roles": [
"director",
"signer"
],
"id_type": "nik",
"id_number_last4": "0001",
"has_id_number": true,
"nationality": "ID",
"ownership_pct": 60
},
{
"id": "1ee2a98e-80c7-4f69-979b-8971e56a36b2",
"full_name": "Bima Santoso",
"position": "Director",
"roles": [
"director",
"beneficial_owner"
],
"id_type": "nik",
"id_number_last4": "0002",
"has_id_number": true,
"nationality": "ID",
"ownership_pct": 40
}
],
"documents": [],
"change_requests": [],
"submitted_at": null,
"decided_at": null,
"decision_reason": null,
"risk_tier_assigned": null,
"updated_at": "2026-10-13T17:13:17.971Z"
},
"progress": {
"steps": {
"company": true,
"people": true,
"bank": true,
"documents": false
},
"done": 3,
"total": 4,
"missing_documents": [
{
"key": "akta_pendirian",
"type": "akta_pendirian",
"label": "Akta pendirian",
"hint": "The deed of establishment.",
"required": true,
"person_id": null
},
{
"key": "sk_kemenkumham",
"type": "sk_kemenkumham",
"label": "SK Kemenkumham",
"hint": "The Ministry of Law approval for the deed.",
"required": true,
"person_id": null
}
],
"complete": false
},
"slots": [
{
"key": "akta_pendirian",
"type": "akta_pendirian",
"label": "Akta pendirian",
"hint": "The deed of establishment.",
"required": true,
"person_id": null
},
{
"key": "akta_perubahan",
"type": "akta_perubahan",
"label": "Latest akta perubahan",
"hint": "The most recent amendment deed, if the company has one.",
"required": false,
"person_id": null
}
],
"history": [
{
"version": 1,
"status": "draft",
"submitted_at": null,
"decided_at": null,
"decision_reason": null
}
],
"review": {
"min_days": 1,
"max_days": 3,
"due_by": null
},
"can_edit": true
}
}Lists are cut to the first 2 items here.
Errors
| Status | Code | Cause |
|---|---|---|
400 | INVALID_BODY | A field has the wrong format, see error.details |
404 | INVITE_INVALID | The token is malformed, forged or revoked |
410 | INVITE_EXPIRED | The link has expired |
403 | INVITE_CLAIMED | Another user already opened this link |
409 | APPLICATION_LOCKED | The application is submitted and waiting for review |
401 | UNAUTHORIZED, SESSION_EXPIRED, INVALID_TOKEN | No session or key, the session expired, or the key is wrong or revoked |
403 | CSRF_FAILED | Session call without the matching X-CSRF-Token header |
429 | RATE_LIMITED | Over the rate limit. Wait for Retry-After seconds |
Upload a counterparty document file
The same checks and answer as Upload a document file.
- Auth: Dashboard session of the user who opened the link first.
- Rate limit: 30 a minute per user and 60 a minute per IP address for this route, plus the shared 300 a minute.
- Idempotency: Not supported. A retry stores another file; unattached uploads are deleted after a day.
Path parameters
| Name | Type | Description |
|---|---|---|
token | string | The token at the end of the link |
Body (multipart)
| Name | Type | Required | Description |
|---|---|---|---|
file | file | Yes | The document, PDF, JPG, PNG or WebP, at most 10 MB |
Example request
curl -X POST https://api.rekberpay.com/v1/kyb/invite/$KYB_INVITE_TOKEN/uploads \
-b cookies.txt \
-H "X-CSRF-Token: $CSRF_TOKEN" \
-F "[email protected];type=application/pdf"import { openAsBlob } from 'node:fs';
const form = new FormData();
form.append('file', await openAsBlob('nib.pdf', { type: 'application/pdf' }), 'nib.pdf');
const res = await fetch(`https://api.rekberpay.com/v1/kyb/invite/${process.env.KYB_INVITE_TOKEN}/uploads`, {
method: 'POST',
headers: {
Cookie: process.env.REKBERPAY_SESSION_COOKIE, // the session cookie from POST /v1/auth/login
'X-CSRF-Token': process.env.REKBERPAY_CSRF_TOKEN,
},
body: form,
});
const data = await res.json();import os
import requests
session = requests.Session()
# The session cookie from POST /v1/auth/login
session.cookies.set("__Host-rekber_session", os.environ["REKBERPAY_SESSION"], domain="api.rekberpay.com")
with open("nib.pdf", "rb") as fh:
resp = session.post(
f"https://api.rekberpay.com/v1/kyb/invite/{os.environ['KYB_INVITE_TOKEN']}/uploads",
headers={
"X-CSRF-Token": os.environ["REKBERPAY_CSRF_TOKEN"],
},
files={"file": ("nib.pdf", fh, "application/pdf")},
timeout=60,
)
print(resp.status_code, resp.json())<?php
$ch = curl_init('https://api.rekberpay.com/v1/kyb/invite/' . getenv('KYB_INVITE_TOKEN') . '/uploads');
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_COOKIEJAR => 'cookies.txt',
CURLOPT_COOKIEFILE => 'cookies.txt',
CURLOPT_HTTPHEADER => [
'X-CSRF-Token: ' . getenv('REKBERPAY_CSRF_TOKEN'),
],
CURLOPT_POSTFIELDS => ['file' => new CURLFile('nib.pdf', 'application/pdf', 'nib.pdf')],
]);
$response = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
curl_close($ch);Example response
201 Created
{
"success": true,
"data": {
"upload_id": "8243ffd8-3532-43fb-8063-053a639603dc/1791911597978_17b5bc5abd2ec7faa21c128bff20629a.pdf",
"mime": "application/pdf",
"size_bytes": 166,
"sha256": "ada1c8a6c1878e7232df92a86849e8136f78db3ea28124d9c1c6b3cdc941b5ce"
}
}Errors
| Status | Code | Cause |
|---|---|---|
400 | TYPE_MISMATCH, TYPE_NOT_ALLOWED, POLYGLOT, PDF_ENCRYPTED, PDF_ACTIVE_CONTENT | The file was refused, see Upload a document file |
413 | FILE_TOO_LARGE | Over 10 MB |
404 | INVITE_INVALID | The token is malformed, forged or revoked |
403 | INVITE_CLAIMED | Another user already opened this link |
401 | UNAUTHORIZED, SESSION_EXPIRED, INVALID_TOKEN | No session or key, the session expired, or the key is wrong or revoked |
403 | CSRF_FAILED | Session call without the matching X-CSRF-Token header |
429 | RATE_LIMITED | Over the rate limit. Wait for Retry-After seconds |
Attach a counterparty document
The same body and rules as Attach a document.
- Auth: Dashboard session of the user who opened the link first.
- Rate limit: 30 a minute per user for this route, plus the shared 300 a minute.
- Idempotency: Not needed: an upload can only be attached once.
Path parameters
| Name | Type | Description |
|---|---|---|
token | string | The token at the end of the link |
Body (JSON)
| Name | Type | Required | Description |
|---|---|---|---|
upload_id | string | Yes | Your own unattached upload |
doc_type | string | Yes | See Attach a document |
person_id | UUID | For ktp and passport | A person from application.people |
Example request
curl -X POST https://api.rekberpay.com/v1/kyb/invite/$KYB_INVITE_TOKEN/documents \
-b cookies.txt \
-H "X-CSRF-Token: $CSRF_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"upload_id": "0b6c8a52-1f0e-4d4a-9a55-6e8c2f2b1c11/1760367000123_4f9c2d7e8a1b3c5d6e7f8091a2b3c4d5.pdf",
"doc_type": "bank_proof"
}'const res = await fetch(`https://api.rekberpay.com/v1/kyb/invite/${process.env.KYB_INVITE_TOKEN}/documents`, {
method: 'POST',
headers: {
Cookie: process.env.REKBERPAY_SESSION_COOKIE, // the session cookie from POST /v1/auth/login
'X-CSRF-Token': process.env.REKBERPAY_CSRF_TOKEN,
'Content-Type': 'application/json',
},
body: JSON.stringify({
"upload_id": "0b6c8a52-1f0e-4d4a-9a55-6e8c2f2b1c11/1760367000123_4f9c2d7e8a1b3c5d6e7f8091a2b3c4d5.pdf",
"doc_type": "bank_proof"
}),
});
const data = await res.json();import os
import requests
session = requests.Session()
# The session cookie from POST /v1/auth/login
session.cookies.set("__Host-rekber_session", os.environ["REKBERPAY_SESSION"], domain="api.rekberpay.com")
resp = session.post(
f"https://api.rekberpay.com/v1/kyb/invite/{os.environ['KYB_INVITE_TOKEN']}/documents",
headers={
"X-CSRF-Token": os.environ["REKBERPAY_CSRF_TOKEN"],
},
json={
"upload_id": "0b6c8a52-1f0e-4d4a-9a55-6e8c2f2b1c11/1760367000123_4f9c2d7e8a1b3c5d6e7f8091a2b3c4d5.pdf",
"doc_type": "bank_proof"
},
timeout=30,
)
print(resp.status_code, resp.json())<?php
$ch = curl_init('https://api.rekberpay.com/v1/kyb/invite/' . getenv('KYB_INVITE_TOKEN') . '/documents');
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_COOKIEJAR => 'cookies.txt',
CURLOPT_COOKIEFILE => 'cookies.txt',
CURLOPT_HTTPHEADER => [
'X-CSRF-Token: ' . getenv('REKBERPAY_CSRF_TOKEN'),
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => json_encode([
'upload_id' => '0b6c8a52-1f0e-4d4a-9a55-6e8c2f2b1c11/1760367000123_4f9c2d7e8a1b3c5d6e7f8091a2b3c4d5.pdf',
'doc_type' => 'bank_proof',
]),
]);
$response = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
curl_close($ch);Example response
201 Created
{
"success": true,
"data": {
"view": "full",
"subject": {
"type": "counterparty",
"id": "6088d36c-5793-4f82-a024-34f064971d12",
"name": "PT Mitra Kargo MV6XWDGH",
"kyb_status": "not_started",
"invited_by": "Sinar Laut MV6XWDGH"
},
"application": {
"id": "78d3f2d7-1eed-4434-a86a-8be3941965fe",
"version": 1,
"status": "draft",
"entity_type": "pt",
"legal_name": "PT Mitra Kargo MV6XWDGH",
"country_code": "ID",
"nib": "9121911597966",
"npwp": "012345678901000",
"address": "Jl. Pelabuhan Raya 12, Tanjung Priok, Jakarta Utara 14310",
"industry": "Logistics",
"monthly_volume": "1b_10b",
"website": "https://sinarlaut.example",
"bank": {
"bank_name": "BCA",
"bank_account_holder": "PT Mitra Kargo MV6XWDGH",
"account_number_last4": "2219",
"has_account_number": true
},
"people": [
{
"id": "c5e85da2-a7c2-4f92-b910-e212afa1e1a0",
"full_name": "Ayu Pratiwi",
"position": "President Director",
"roles": [
"director",
"signer"
],
"id_type": "nik",
"id_number_last4": "0001",
"has_id_number": true,
"nationality": "ID",
"ownership_pct": 60
},
{
"id": "1ee2a98e-80c7-4f69-979b-8971e56a36b2",
"full_name": "Bima Santoso",
"position": "Director",
"roles": [
"director",
"beneficial_owner"
],
"id_type": "nik",
"id_number_last4": "0002",
"has_id_number": true,
"nationality": "ID",
"ownership_pct": 40
}
],
"documents": [
{
"id": "2a370bb3-76c2-4416-afc8-ef0d4d621a1b",
"doc_type": "akta_pendirian",
"person_id": null,
"original_name": "akta_pendirian.pdf",
"mime": "application/pdf",
"size_bytes": 166,
"status": "uploaded",
"reviewer_note": null,
"created_at": "2026-10-13T17:13:17.982Z"
}
],
"change_requests": [],
"submitted_at": null,
"decided_at": null,
"decision_reason": null,
"risk_tier_assigned": null,
"updated_at": "2026-10-13T17:13:17.982Z"
},
"progress": {
"steps": {
"company": true,
"people": true,
"bank": true,
"documents": false
},
"done": 3,
"total": 4,
"missing_documents": [
{
"key": "sk_kemenkumham",
"type": "sk_kemenkumham",
"label": "SK Kemenkumham",
"hint": "The Ministry of Law approval for the deed.",
"required": true,
"person_id": null
},
{
"key": "nib",
"type": "nib",
"label": "NIB (OSS)",
"hint": "Your business registration number from OSS.",
"required": true,
"person_id": null
}
],
"complete": false
},
"slots": [
{
"key": "akta_pendirian",
"type": "akta_pendirian",
"label": "Akta pendirian",
"hint": "The deed of establishment.",
"required": true,
"person_id": null
},
{
"key": "akta_perubahan",
"type": "akta_perubahan",
"label": "Latest akta perubahan",
"hint": "The most recent amendment deed, if the company has one.",
"required": false,
"person_id": null
}
],
"history": [
{
"version": 1,
"status": "draft",
"submitted_at": null,
"decided_at": null,
"decision_reason": null
}
],
"review": {
"min_days": 1,
"max_days": 3,
"due_by": null
},
"can_edit": true
}
}Lists are cut to the first 2 items here.
Errors
| Status | Code | Cause |
|---|---|---|
404 | UPLOAD_NOT_FOUND | No unattached upload of yours with this ID |
404 | INVITE_INVALID | The token is malformed, forged or revoked |
409 | APPLICATION_LOCKED | The application is submitted and waiting for review |
401 | UNAUTHORIZED, SESSION_EXPIRED, INVALID_TOKEN | No session or key, the session expired, or the key is wrong or revoked |
403 | CSRF_FAILED | Session call without the matching X-CSRF-Token header |
429 | RATE_LIMITED | Over the rate limit. Wait for Retry-After seconds |
Remove a counterparty document
Removes a document from the counterparty's draft.
- Auth: Dashboard session of the user who opened the link first.
- Rate limit: 30 a minute per user for this route, plus the shared 300 a minute.
- Idempotency: Not needed. A second call answers
404.
Path parameters
| Name | Type | Description |
|---|---|---|
token | string | The token at the end of the link |
id | UUID | The document id |
Example request
curl -X DELETE https://api.rekberpay.com/v1/kyb/invite/$KYB_INVITE_TOKEN/documents/d0c00001-d0c0-4d0c-8d0c-d0c00000000a \
-b cookies.txt \
-H "X-CSRF-Token: $CSRF_TOKEN"const res = await fetch(`https://api.rekberpay.com/v1/kyb/invite/${process.env.KYB_INVITE_TOKEN}/documents/d0c00001-d0c0-4d0c-8d0c-d0c00000000a`, {
method: 'DELETE',
headers: {
Cookie: process.env.REKBERPAY_SESSION_COOKIE, // the session cookie from POST /v1/auth/login
'X-CSRF-Token': process.env.REKBERPAY_CSRF_TOKEN,
},
});
const data = await res.json();import os
import requests
session = requests.Session()
# The session cookie from POST /v1/auth/login
session.cookies.set("__Host-rekber_session", os.environ["REKBERPAY_SESSION"], domain="api.rekberpay.com")
resp = session.delete(
f"https://api.rekberpay.com/v1/kyb/invite/{os.environ['KYB_INVITE_TOKEN']}/documents/d0c00001-d0c0-4d0c-8d0c-d0c00000000a",
headers={
"X-CSRF-Token": os.environ["REKBERPAY_CSRF_TOKEN"],
},
timeout=30,
)
print(resp.status_code, resp.json())<?php
$ch = curl_init('https://api.rekberpay.com/v1/kyb/invite/' . getenv('KYB_INVITE_TOKEN') . '/documents/d0c00001-d0c0-4d0c-8d0c-d0c00000000a');
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'DELETE',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_COOKIEJAR => 'cookies.txt',
CURLOPT_COOKIEFILE => 'cookies.txt',
CURLOPT_HTTPHEADER => [
'X-CSRF-Token: ' . getenv('REKBERPAY_CSRF_TOKEN'),
],
]);
$response = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
curl_close($ch);Example response
200 OK
{
"success": true,
"data": {
"view": "full",
"subject": {
"type": "counterparty",
"id": "6088d36c-5793-4f82-a024-34f064971d12",
"name": "PT Mitra Kargo MV6XWDGH",
"kyb_status": "not_started",
"invited_by": "Sinar Laut MV6XWDGH"
},
"application": {
"id": "78d3f2d7-1eed-4434-a86a-8be3941965fe",
"version": 1,
"status": "draft",
"entity_type": "pt",
"legal_name": "PT Mitra Kargo MV6XWDGH",
"country_code": "ID",
"nib": "9121911597966",
"npwp": "012345678901000",
"address": "Jl. Pelabuhan Raya 12, Tanjung Priok, Jakarta Utara 14310",
"industry": "Logistics",
"monthly_volume": "1b_10b",
"website": "https://sinarlaut.example",
"bank": {
"bank_name": "BCA",
"bank_account_holder": "PT Mitra Kargo MV6XWDGH",
"account_number_last4": "2219",
"has_account_number": true
},
"people": [
{
"id": "c5e85da2-a7c2-4f92-b910-e212afa1e1a0",
"full_name": "Ayu Pratiwi",
"position": "President Director",
"roles": [
"director",
"signer"
],
"id_type": "nik",
"id_number_last4": "0001",
"has_id_number": true,
"nationality": "ID",
"ownership_pct": 60
},
{
"id": "1ee2a98e-80c7-4f69-979b-8971e56a36b2",
"full_name": "Bima Santoso",
"position": "Director",
"roles": [
"director",
"beneficial_owner"
],
"id_type": "nik",
"id_number_last4": "0002",
"has_id_number": true,
"nationality": "ID",
"ownership_pct": 40
}
],
"documents": [
{
"id": "2a370bb3-76c2-4416-afc8-ef0d4d621a1b",
"doc_type": "akta_pendirian",
"person_id": null,
"original_name": "akta_pendirian.pdf",
"mime": "application/pdf",
"size_bytes": 166,
"status": "uploaded",
"reviewer_note": null,
"created_at": "2026-10-13T17:13:17.982Z"
},
{
"id": "c3825c07-d037-4e0d-9cc3-afe62941a0c3",
"doc_type": "ktp",
"person_id": "c5e85da2-a7c2-4f92-b910-e212afa1e1a0",
"original_name": "ktp.jpg",
"mime": "image/jpeg",
"size_bytes": 1769,
"status": "uploaded",
"reviewer_note": null,
"created_at": "2026-10-13T17:13:18.029Z"
}
],
"change_requests": [],
"submitted_at": null,
"decided_at": null,
"decision_reason": null,
"risk_tier_assigned": null,
"updated_at": "2026-10-13T17:13:18.052Z"
},
"progress": {
"steps": {
"company": true,
"people": true,
"bank": true,
"documents": false
},
"done": 3,
"total": 4,
"missing_documents": [
{
"key": "bank_proof",
"type": "bank_proof",
"label": "Proof of bank account",
"hint": "A statement cover or a bank letter showing the account holder name.",
"required": true,
"person_id": null
}
],
"complete": false
},
"slots": [
{
"key": "akta_pendirian",
"type": "akta_pendirian",
"label": "Akta pendirian",
"hint": "The deed of establishment.",
"required": true,
"person_id": null
},
{
"key": "akta_perubahan",
"type": "akta_perubahan",
"label": "Latest akta perubahan",
"hint": "The most recent amendment deed, if the company has one.",
"required": false,
"person_id": null
}
],
"history": [
{
"version": 1,
"status": "draft",
"submitted_at": null,
"decided_at": null,
"decision_reason": null
}
],
"review": {
"min_days": 1,
"max_days": 3,
"due_by": null
},
"can_edit": true
}
}Lists are cut to the first 2 items here.
Errors
| Status | Code | Cause |
|---|---|---|
404 | NOT_FOUND | No document with this ID on the current draft |
409 | APPLICATION_LOCKED | The application is submitted |
401 | UNAUTHORIZED, SESSION_EXPIRED, INVALID_TOKEN | No session or key, the session expired, or the key is wrong or revoked |
403 | CSRF_FAILED | Session call without the matching X-CSRF-Token header |
429 | RATE_LIMITED | Over the rate limit. Wait for Retry-After seconds |
Get a counterparty document link
A 5 minute link to one of the counterparty's documents, signed for you. See Get a document link.
- Auth: Dashboard session of the user who opened the link first.
- Rate limit: 120 a minute per user for this route, plus the shared 300 a minute.
- Idempotency: Not needed. Reading has no side effects.
Path parameters
| Name | Type | Description |
|---|---|---|
token | string | The token at the end of the link |
id | UUID | The document id |
Example request
curl https://api.rekberpay.com/v1/kyb/invite/$KYB_INVITE_TOKEN/documents/d0c00001-d0c0-4d0c-8d0c-d0c00000000a/url \
-b cookies.txtconst res = await fetch(`https://api.rekberpay.com/v1/kyb/invite/${process.env.KYB_INVITE_TOKEN}/documents/d0c00001-d0c0-4d0c-8d0c-d0c00000000a/url`, {
headers: {
Cookie: process.env.REKBERPAY_SESSION_COOKIE, // the session cookie from POST /v1/auth/login
},
});
const data = await res.json();import os
import requests
session = requests.Session()
# The session cookie from POST /v1/auth/login
session.cookies.set("__Host-rekber_session", os.environ["REKBERPAY_SESSION"], domain="api.rekberpay.com")
resp = session.get(
f"https://api.rekberpay.com/v1/kyb/invite/{os.environ['KYB_INVITE_TOKEN']}/documents/d0c00001-d0c0-4d0c-8d0c-d0c00000000a/url",
timeout=30,
)
print(resp.status_code, resp.json())<?php
$ch = curl_init('https://api.rekberpay.com/v1/kyb/invite/' . getenv('KYB_INVITE_TOKEN') . '/documents/d0c00001-d0c0-4d0c-8d0c-d0c00000000a/url');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_COOKIEJAR => 'cookies.txt',
CURLOPT_COOKIEFILE => 'cookies.txt',
]);
$response = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
curl_close($ch);Example response
200 OK
{
"success": true,
"data": {
"url": "/v1/kyb/files/4b329f64-637a-4cea-8a39-323c31b3c5c0?exp=1791911898&sig=N4cioEqVQd2AjhEgQJXTDt3oVzg9EolhkCiZRXXvGWk",
"expires_at": "2026-10-13T17:18:18.000Z"
}
}Errors
| Status | Code | Cause |
|---|---|---|
404 | NOT_FOUND | No document with this ID on this application |
401 | UNAUTHORIZED, SESSION_EXPIRED, INVALID_TOKEN | No session or key, the session expired, or the key is wrong or revoked |
403 | CSRF_FAILED | Session call without the matching X-CSRF-Token header |
429 | RATE_LIMITED | Over the rate limit. Wait for Retry-After seconds |
Submit a counterparty application
The same checks as Submit for review. The counterparty's kyb_status moves to pending, and the workspace that sent the link sees the decision on the counterparty.
- Auth: Dashboard session of the user who opened the link first.
- Rate limit: 10 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 returns the first answer withIdempotent-Replay: true.
Path parameters
| Name | Type | Description |
|---|---|---|
token | string | The token at the end of the link |
Example request
curl -X POST https://api.rekberpay.com/v1/kyb/invite/$KYB_INVITE_TOKEN/submit \
-b cookies.txt \
-H "X-CSRF-Token: $CSRF_TOKEN" \
-H "Idempotency-Key: 99999995-9999-4999-8999-999999999999" \
-H "Content-Type: application/json" \
-d '{}'const res = await fetch(`https://api.rekberpay.com/v1/kyb/invite/${process.env.KYB_INVITE_TOKEN}/submit`, {
method: 'POST',
headers: {
Cookie: process.env.REKBERPAY_SESSION_COOKIE, // the session cookie from POST /v1/auth/login
'X-CSRF-Token': process.env.REKBERPAY_CSRF_TOKEN,
'Idempotency-Key': '99999995-9999-4999-8999-999999999999',
'Content-Type': 'application/json',
},
body: JSON.stringify({}),
});
const data = await res.json();import os
import requests
session = requests.Session()
# The session cookie from POST /v1/auth/login
session.cookies.set("__Host-rekber_session", os.environ["REKBERPAY_SESSION"], domain="api.rekberpay.com")
resp = session.post(
f"https://api.rekberpay.com/v1/kyb/invite/{os.environ['KYB_INVITE_TOKEN']}/submit",
headers={
"X-CSRF-Token": os.environ["REKBERPAY_CSRF_TOKEN"],
"Idempotency-Key": "99999995-9999-4999-8999-999999999999",
},
json={},
timeout=30,
)
print(resp.status_code, resp.json())<?php
$ch = curl_init('https://api.rekberpay.com/v1/kyb/invite/' . getenv('KYB_INVITE_TOKEN') . '/submit');
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_COOKIEJAR => 'cookies.txt',
CURLOPT_COOKIEFILE => 'cookies.txt',
CURLOPT_HTTPHEADER => [
'X-CSRF-Token: ' . getenv('REKBERPAY_CSRF_TOKEN'),
'Idempotency-Key: 99999995-9999-4999-8999-999999999999',
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => json_encode(new stdClass()),
]);
$response = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
curl_close($ch);Example response
200 OK
{
"success": true,
"data": {
"view": "full",
"subject": {
"type": "counterparty",
"id": "6088d36c-5793-4f82-a024-34f064971d12",
"name": "PT Mitra Kargo MV6XWDGH",
"kyb_status": "pending",
"invited_by": "Sinar Laut MV6XWDGH"
},
"application": {
"id": "78d3f2d7-1eed-4434-a86a-8be3941965fe",
"version": 1,
"status": "submitted",
"entity_type": "pt",
"legal_name": "PT Mitra Kargo MV6XWDGH",
"country_code": "ID",
"nib": "9121911597966",
"npwp": "012345678901000",
"address": "Jl. Pelabuhan Raya 12, Tanjung Priok, Jakarta Utara 14310",
"industry": "Logistics",
"monthly_volume": "1b_10b",
"website": "https://sinarlaut.example",
"bank": {
"bank_name": "BCA",
"bank_account_holder": "PT Mitra Kargo MV6XWDGH",
"account_number_last4": "2219",
"has_account_number": true
},
"people": [
{
"id": "c5e85da2-a7c2-4f92-b910-e212afa1e1a0",
"full_name": "Ayu Pratiwi",
"position": "President Director",
"roles": [
"director",
"signer"
],
"id_type": "nik",
"id_number_last4": "0001",
"has_id_number": true,
"nationality": "ID",
"ownership_pct": 60
},
{
"id": "1ee2a98e-80c7-4f69-979b-8971e56a36b2",
"full_name": "Bima Santoso",
"position": "Director",
"roles": [
"director",
"beneficial_owner"
],
"id_type": "nik",
"id_number_last4": "0002",
"has_id_number": true,
"nationality": "ID",
"ownership_pct": 40
}
],
"documents": [
{
"id": "2a370bb3-76c2-4416-afc8-ef0d4d621a1b",
"doc_type": "akta_pendirian",
"person_id": null,
"original_name": "akta_pendirian.pdf",
"mime": "application/pdf",
"size_bytes": 166,
"status": "uploaded",
"reviewer_note": null,
"created_at": "2026-10-13T17:13:17.982Z"
},
{
"id": "c5d75bc2-afd2-4e8c-9521-c37be010749a",
"doc_type": "bank_proof",
"person_id": null,
"original_name": "bank.pdf",
"mime": "application/pdf",
"size_bytes": 156,
"status": "uploaded",
"reviewer_note": null,
"created_at": "2026-10-13T17:13:18.060Z"
}
],
"change_requests": [],
"submitted_at": "2026-10-13T17:13:18.067Z",
"decided_at": null,
"decision_reason": null,
"risk_tier_assigned": null,
"updated_at": "2026-10-13T17:13:18.067Z"
},
"progress": {
"steps": {
"company": true,
"people": true,
"bank": true,
"documents": true
},
"done": 4,
"total": 4,
"missing_documents": [],
"complete": true
},
"slots": [
{
"key": "akta_pendirian",
"type": "akta_pendirian",
"label": "Akta pendirian",
"hint": "The deed of establishment.",
"required": true,
"person_id": null
},
{
"key": "akta_perubahan",
"type": "akta_perubahan",
"label": "Latest akta perubahan",
"hint": "The most recent amendment deed, if the company has one.",
"required": false,
"person_id": null
}
],
"history": [
{
"version": 1,
"status": "submitted",
"submitted_at": "2026-10-13T17:13:18.067Z",
"decided_at": null,
"decision_reason": null
}
],
"review": {
"min_days": 1,
"max_days": 3,
"due_by": "2026-10-18T17:13:18.067Z"
},
"can_edit": false
}
}Lists are cut to the first 2 items here.
Errors
| Status | Code | Cause |
|---|---|---|
422 | INCOMPLETE | A step is not complete, see error.details |
409 | APPLICATION_LOCKED | Already submitted |
404 | INVITE_INVALID | The token is malformed, forged or revoked |
401 | UNAUTHORIZED, SESSION_EXPIRED, INVALID_TOKEN | No session or key, the session expired, or the key is wrong or revoked |
403 | CSRF_FAILED | Session call without the matching X-CSRF-Token header |
429 | RATE_LIMITED | Over the rate limit. Wait for Retry-After seconds |