Authentication and API keys
The API accepts three kinds of credentials:
| Credential | Looks like | Use it for |
|---|---|---|
| Session cookie | Set by POST /v1/auth/login | Browser apps. Writes also need X-CSRF-Token |
| Personal API key | Authorization: Bearer <43 characters> | Server code that acts as one RekberPay user |
| Workspace API key | Authorization: Bearer rpe_... | Server code for an enterprise workspace, under /v1/enterprise only |
This guide signs in, creates a personal key, uses it, and then sets up a workspace key. Signed partner links are a separate mechanism; see Authentication.
1. Sign in
Sign in once with the account that your integration acts as. The answer sets the session cookie and returns csrf_token.
curl -X POST https://api.rekberpay.com/v1/auth/login \
-c cookies.txt \
-H "Content-Type: application/json" \
-d '{
"email": "[email protected]",
"password": "Docs-3zv7kaA8-hp4"
}'const res = await fetch('https://api.rekberpay.com/v1/auth/login', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
email: "[email protected]",
password: "Docs-3zv7kaA8-hp4"
}),
});
const data = await res.json();
// Keep the session cookie and the CSRF token for later calls.
const cookie = res.headers.get('set-cookie').split(';')[0];
const csrfToken = data.data.csrf_token;import os
import requests
session = requests.Session()
resp = session.post(
"https://api.rekberpay.com/v1/auth/login",
json={
"email": "[email protected]",
"password": "Docs-3zv7kaA8-hp4",
},
timeout=30,
)
print(resp.status_code, resp.json())
csrf_token = resp.json()["data"]["csrf_token"]<?php
$ch = curl_init('https://api.rekberpay.com/v1/auth/login');
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_COOKIEJAR => 'cookies.txt',
CURLOPT_COOKIEFILE => 'cookies.txt',
CURLOPT_HTTPHEADER => [
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => json_encode([
'email' => '[email protected]',
'password' => 'Docs-3zv7kaA8-hp4',
]),
]);
$response = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
curl_close($ch);200 OK
{
"success": true,
"data": {
"user": {
"id": "11111110-1111-4111-8111-111111111111",
"email": "[email protected]",
"role": "user",
"full_name": "Rina Wijaya",
"username": "rinadocs",
"phone": null,
"kyc_status": "verified",
"balance": 0,
"is_suspended": false,
"is_verified": false,
"two_factor_method": "none",
"dashboard_tour_completed_at": null,
"social_instagram": null,
"social_shopee": null,
"social_tiktokshop": null,
"created_at": "2026-10-13T14:18:52.168Z",
"has_password": true,
"email_verification_required": false
},
"csrf_token": "Gq7Lm2Vx9Rt4Kp1Ns8Bw3Hz6Yc0Df5J"
}
}A wrong password answers 401 INVALID_CREDENTIALS. The same answer comes back for an email that has no account, so the call cannot be used to find accounts:
401 Unauthorized
{
"success": false,
"error": {
"code": "INVALID_CREDENTIALS",
"message": "Invalid credentials.",
"requires_captcha": false
}
}If the account has two-step sign-in by email, the answer is { "requires_2fa": true, "ticket": "..." } instead; send the ticket and the emailed code to POST /v1/auth/login/2fa.
2. Create a personal API key
With the session from step 1, create the key. The plaintext is in the answer once. Store it in your secret manager; RekberPay keeps only a hash.
curl -X POST https://api.rekberpay.com/v1/auth/me/api-key/rotate \
-b cookies.txt \
-H "X-CSRF-Token: $CSRF_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"label": "docs"
}'const res = await fetch('https://api.rekberpay.com/v1/auth/me/api-key/rotate', {
method: 'POST',
headers: {
Cookie: process.env.REKBERPAY_SESSION_COOKIE, // '__Host-rekber_session=...' from POST /v1/auth/login
'X-CSRF-Token': process.env.REKBERPAY_CSRF_TOKEN,
'Content-Type': 'application/json',
},
body: JSON.stringify({
label: "docs"
}),
});
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/auth/me/api-key/rotate",
headers={
"X-CSRF-Token": os.environ["REKBERPAY_CSRF_TOKEN"],
},
json={
"label": "docs",
},
timeout=30,
)
print(resp.status_code, resp.json())<?php
$ch = curl_init('https://api.rekberpay.com/v1/auth/me/api-key/rotate');
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([
'label' => 'docs',
]),
]);
$response = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
curl_close($ch);200 OK
{
"success": true,
"data": {
"api_key": "rpk_kM3vT8qR1xZ6nB4wY9pL2sD7fH0jC5gA3eU8iO1tN6r"
}
}New keys start with rpk_. Older keys without the prefix keep working until you rotate them.
Calling this again creates a new key and revokes the old one at once, which is how you rotate. A few things to know:
- At most 5 rotations per account per hour. The sixth gets
429 ROTATE_LIMITEDand the current key keeps working. labelis optional. When you send it, it must be up to 64 letters, digits, spaces or_ . : -, starting with a letter or digit. Anything else, or a body that is not a JSON object, gets400 INVALID_BODYand the key stays as it is.- Every rotation emails the account owner, with the time and IP address. The email never contains the key.
- Signing in on the RekberPay phone apps no longer changes the key. Only you do, from Generate Baru in the dashboard or with this call.
- A password reset or change also revokes the key.
GET /v1/auth/me/api-key shows a masked preview of the live key, when it was created and when it was last used. Every field is null when there is no live key. Check last_used_at before you rotate: a recent time means something still uses the key.
200 OK
{
"success": true,
"data": {
"api_key": "rpk_kM•••••••••••••••••••••••••••••••••••••tN6r",
"created_at": "2026-10-18T03:04:05.000Z",
"last_used_at": "2026-10-18T09:12:44.000Z"
}
}3. Call the API with the key
Send the key as a Bearer token. Key calls need no cookie and no CSRF token.
curl https://api.rekberpay.com/v1/escrows/stats \
-H "Authorization: Bearer $REKBERPAY_API_KEY"const res = await fetch('https://api.rekberpay.com/v1/escrows/stats', {
headers: {
Authorization: `Bearer ${process.env.REKBERPAY_API_KEY}`,
},
});
const data = await res.json();import os
import requests
resp = requests.get(
"https://api.rekberpay.com/v1/escrows/stats",
headers={
"Authorization": f"Bearer {os.environ['REKBERPAY_API_KEY']}",
},
timeout=30,
)
print(resp.status_code, resp.json())<?php
$ch = curl_init('https://api.rekberpay.com/v1/escrows/stats');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('REKBERPAY_API_KEY'),
],
]);
$response = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
curl_close($ch);200 OK
{
"success": true,
"data": {
"total_transactions": 4,
"total_value": 23500000,
"active_escrows": 2,
"disputed_count": 1,
"released_value": 250000,
"held_value": 300000,
"buyer_held_value": 300000,
"seller_receivable_value": 0,
"active_value": 300000
}
}4. Know the authentication errors
No credentials at all:
401 Unauthorized
{
"success": false,
"error": {
"code": "UNAUTHORIZED",
"message": "No session."
}
}A wrong or revoked key:
401 Unauthorized
{
"success": false,
"error": {
"code": "INVALID_TOKEN",
"message": "API key invalid or revoked."
}
}A session write without the X-CSRF-Token header:
403 Forbidden
{
"success": false,
"error": {
"code": "CSRF_FAILED",
"message": "CSRF token mismatch."
}
}5. Workspace API keys for enterprise
Enterprise integrations should use a workspace key instead of a personal key. It belongs to the workspace, has a read or read and write scope, and cannot manage members, settings, keys or webhooks. An admin creates it from a dashboard session:
curl -X POST https://api.rekberpay.com/v1/enterprise/api-keys \
-b cookies.txt \
-H "X-CSRF-Token: $CSRF_TOKEN" \
-H "X-Enterprise-Account: $WORKSPACE_ID" \
-H "Content-Type: application/json" \
-d '{
"label": "ERP sync",
"scopes": [
"read",
"write"
]
}'const res = await fetch('https://api.rekberpay.com/v1/enterprise/api-keys', {
method: 'POST',
headers: {
Cookie: process.env.REKBERPAY_SESSION_COOKIE, // '__Host-rekber_session=...' 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({
label: "ERP sync",
scopes: [
"read",
"write"
]
}),
});
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/api-keys",
headers={
"X-CSRF-Token": os.environ["REKBERPAY_CSRF_TOKEN"],
"X-Enterprise-Account": os.environ["WORKSPACE_ID"],
},
json={
"label": "ERP sync",
"scopes": [
"read",
"write",
],
},
timeout=30,
)
print(resp.status_code, resp.json())<?php
$ch = curl_init('https://api.rekberpay.com/v1/enterprise/api-keys');
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([
'label' => 'ERP sync',
'scopes' => [
'read',
'write',
],
]),
]);
$response = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
curl_close($ch);201 Created
{
"success": true,
"data": {
"id": "22222223-2222-4222-8222-222222222222",
"label": "ERP sync",
"scopes": [
"read",
"write"
],
"key_preview": "rpe_q8Zt…",
"created_by": {
"id": "33333333-3333-4333-8333-333333333333",
"name": null
},
"last_used_at": null,
"created_at": "2026-10-13T14:33:15.355Z",
"key": "rpe_q8Zt3VnK1mW0xPe7Rk2LsY5bHd9JcAf4GuN6oTiQ1vE"
}
}The rules, with real answers:
- It only works under
/v1/enterprise:
401 Unauthorized
{
"success": false,
"error": {
"code": "WORKSPACE_KEY_ONLY",
"message": "Workspace API keys only work on enterprise routes (/v1/enterprise)."
}
}- A
readkey cannot write:
403 Forbidden
{
"success": false,
"error": {
"code": "SCOPE_REQUIRED",
"message": "This API key is read-only. Create a key with the write scope."
}
}- No key can manage keys, so a leaked key cannot make itself permanent:
403 Forbidden
{
"success": false,
"error": {
"code": "KEY_NOT_ALLOWED",
"message": "Workspace API keys cannot call this route. Sign in to the dashboard instead."
}
}Revoke a key with DELETE /v1/enterprise/api-keys/:id. It stops working at once.
Checklist
- Keep keys on your server. Never ship one in a browser or mobile app.
- Read keys from the environment or a secret manager, never from source code.
- Rotate a personal key by creating a new one; revoke workspace keys you no longer use.
- Use one workspace key per integration, so you can revoke one without stopping the others.