Skip to content

Authentication and API keys ​

The API accepts three kinds of credentials:

CredentialLooks likeUse it for
Session cookieSet by POST /v1/auth/loginBrowser apps. Writes also need X-CSRF-Token
Personal API keyAuthorization: Bearer <43 characters>Server code that acts as one RekberPay user
Workspace API keyAuthorization: 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.

bash
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"
  }'
js
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;
python
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
<?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

json
{
    "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

json
{
    "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.

bash
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"
  }'
js
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();
python
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
<?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

json
{
    "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_LIMITED and the current key keeps working.
  • label is 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, gets 400 INVALID_BODY and 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

json
{
    "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.

bash
curl https://api.rekberpay.com/v1/escrows/stats \
  -H "Authorization: Bearer $REKBERPAY_API_KEY"
js
const res = await fetch('https://api.rekberpay.com/v1/escrows/stats', {
    headers: {
        Authorization: `Bearer ${process.env.REKBERPAY_API_KEY}`,
    },
});
const data = await res.json();
python
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
<?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

json
{
    "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

json
{
    "success": false,
    "error": {
        "code": "UNAUTHORIZED",
        "message": "No session."
    }
}

A wrong or revoked key:

401 Unauthorized

json
{
    "success": false,
    "error": {
        "code": "INVALID_TOKEN",
        "message": "API key invalid or revoked."
    }
}

A session write without the X-CSRF-Token header:

403 Forbidden

json
{
    "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:

bash
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"
    ]
  }'
js
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();
python
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
<?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

json
{
    "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

json
{
    "success": false,
    "error": {
        "code": "WORKSPACE_KEY_ONLY",
        "message": "Workspace API keys only work on enterprise routes (/v1/enterprise)."
    }
}
  • A read key cannot write:

403 Forbidden

json
{
    "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

json
{
    "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.

Next steps ​

Escrow, payment services and B2B deals for Indonesian businesses.