Skip to content

Authentication API ​

These endpoints sign a user in and out and manage the personal API key. Most integrations call them once: sign in, create the key, then send the key as Authorization: Bearer <key> on every other call. The concepts, including signed partner links and the CSRF rules, are in Authentication.

Every example on this page was captured from a running API. IDs, keys and tokens are replaced with samples.

Endpoints ​

MethodPathEndpointAuth
POST/v1/auth/loginSign inNone
POST/v1/auth/login/2faSign in with a 2FA codeNone
POST/v1/auth/logoutSign outSession
GET/v1/auth/meGet the signed-in userOptional
PATCH/v1/auth/meUpdate the signed-in userKey or session
GET/v1/auth/me/api-keyGet the personal API keyKey or session
POST/v1/auth/me/api-key/rotateCreate or rotate the personal API keyKey or session

Sign in ​

POST/v1/auth/login

Signs in with email and password. On success the response sets the session cookie (__Host-rekber_session, HttpOnly, Secure) and returns csrf_token. Send that token as X-CSRF-Token on every POST, PATCH, PUT and DELETE made with the cookie.

When the account has two-step sign-in by email, the answer is { "requires_2fa": true, "method": "email", "ticket": "..." } and no cookie is set. RekberPay emails a 6-digit code; finish with POST /v1/auth/login/2fa.

  • Auth: None. A successful call sets the session cookie and returns the CSRF token.
  • Rate limit: Counted per IP address and email. From the third failure a Turnstile token is required, from the sixth the sign-in is locked for a while (423 ACCOUNT_LOCKED), and after ten failures the address gets 429 LOGIN_LOCKED with Retry-After.
  • Idempotency: Not supported. A retry runs the call again.

Body (JSON) ​

NameTypeRequiredDescription
emailstringYesEmail address, at most 254 characters
passwordstringYes1 to 128 characters
turnstile_tokenstringNoCloudflare Turnstile token. Needed when Turnstile is on, and always after a few failed attempts

Example request ​

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);

Example response ​

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"
    }
}
FieldTypeDescription
userobjectThe signed-in user. Same shape as GET /v1/auth/me
csrf_tokenstringSend it as X-CSRF-Token on writes made with this session

Errors ​

StatusCodeCause
400INVALID_BODYEmail or password missing or too long
400TURNSTILE_REQUIREDA few attempts failed, so a Turnstile token is now required. The error has requires_captcha: true
400TURNSTILE_FAILEDThe Turnstile token did not verify
401INVALID_CREDENTIALSWrong email or password. The same answer is given for an unknown email
403ACCOUNT_INACTIVE, SUSPENDEDCorrect password, but the account is closed or suspended
423ACCOUNT_LOCKEDToo many failed attempts. Try again later
429LOGIN_LOCKEDToo many failures from this address. Wait for Retry-After seconds
501TWO_FACTOR_METHOD_UNSUPPORTEDThe account uses a second factor that sign-in does not support yet
429RATE_LIMITEDOver the rate limit. Wait for Retry-After seconds

A real INVALID_CREDENTIALS answer:

401 Unauthorized

json
{
    "success": false,
    "error": {
        "code": "INVALID_CREDENTIALS",
        "message": "Invalid credentials.",
        "requires_captcha": false
    }
}

Sign in with a 2FA code ​

POST/v1/auth/login/2fa

Completes a sign-in that answered requires_2fa: true. Send the ticket from that answer and the 6-digit code from the email. A correct code gives the same answer as a normal sign-in: the session cookie plus user and csrf_token.

The example shows a real answer to an expired ticket. A successful answer has the same shape as Sign in.

  • Auth: None. A successful call sets the session cookie and returns the CSRF token.
  • Rate limit: Counted per IP address and email. From the third failure a Turnstile token is required, from the sixth the sign-in is locked for a while (423 ACCOUNT_LOCKED), and after ten failures the address gets 429 LOGIN_LOCKED with Retry-After.
  • Idempotency: Not supported. A retry runs the call again.

Body (JSON) ​

NameTypeRequiredDescription
ticketstringYesThe ticket from the sign-in answer
codestringYesExactly 6 digits

Example request ​

bash
curl -X POST https://api.rekberpay.com/v1/auth/login/2fa \
  -H "Content-Type: application/json" \
  -d '{
    "ticket": "eyJ0eXAiOiIyZmFfbG9naW4iLCJzdWIiOiIuLi4ifQ.sample",
    "code": "123456"
  }'
js
const res = await fetch('https://api.rekberpay.com/v1/auth/login/2fa', {
    method: 'POST',
    headers: {
        'Content-Type': 'application/json',
    },
    body: JSON.stringify({
        ticket: "eyJ0eXAiOiIyZmFfbG9naW4iLCJzdWIiOiIuLi4ifQ.sample",
        code: "123456"
    }),
});
const data = await res.json();
python
import os

import requests

resp = requests.post(
    "https://api.rekberpay.com/v1/auth/login/2fa",
    json={
        "ticket": "eyJ0eXAiOiIyZmFfbG9naW4iLCJzdWIiOiIuLi4ifQ.sample",
        "code": "123456",
    },
    timeout=30,
)
print(resp.status_code, resp.json())
php
<?php

$ch = curl_init('https://api.rekberpay.com/v1/auth/login/2fa');
curl_setopt_array($ch, [
    CURLOPT_CUSTOMREQUEST => 'POST',
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        'Content-Type: application/json',
    ],
    CURLOPT_POSTFIELDS => json_encode([
        'ticket' => 'eyJ0eXAiOiIyZmFfbG9naW4iLCJzdWIiOiIuLi4ifQ.sample',
        'code' => '123456',
    ]),
]);
$response = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
curl_close($ch);

Example response ​

401 Unauthorized

json
{
    "success": false,
    "error": {
        "code": "INVALID_TICKET",
        "message": "Sesi login telah kadaluarsa. Silakan login kembali."
    }
}

Errors ​

StatusCodeCause
400INVALID_BODYTicket missing or the code is not 6 digits
401INVALID_TICKETThe ticket expired or is unknown. Sign in again
401INVALID_CODEWrong or expired code. error.reason is wrong, expired or exhausted
403ACCOUNT_INACTIVE, SUSPENDEDThe account is closed or suspended
423ACCOUNT_LOCKEDToo many wrong codes. Try again later
429RATE_LIMITEDOver the rate limit. Wait for Retry-After seconds

Sign out ​

POST/v1/auth/logout

Ends the session and clears the cookie. With an API key the call succeeds and changes nothing.

  • Auth: A session cookie. Writes also need X-CSRF-Token.
  • Rate limit: 300 requests a minute per user, shared by every route.
  • Idempotency: Not supported. A retry runs the call again.

Example request ​

bash
curl -X POST https://api.rekberpay.com/v1/auth/logout \
  -b cookies.txt \
  -H "X-CSRF-Token: $CSRF_TOKEN"
js
const res = await fetch('https://api.rekberpay.com/v1/auth/logout', {
    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,
    },
});
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/logout",
    headers={
        "X-CSRF-Token": os.environ["REKBERPAY_CSRF_TOKEN"],
    },
    timeout=30,
)
print(resp.status_code, resp.json())
php
<?php

$ch = curl_init('https://api.rekberpay.com/v1/auth/logout');
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'),
    ],
]);
$response = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
curl_close($ch);

Example response ​

200 OK

json
{
    "success": true
}

Errors ​

StatusCodeCause
401UNAUTHORIZED, SESSION_EXPIRED, INVALID_TOKENNo session or API key, the session expired, or the key is wrong or revoked
401WORKSPACE_KEY_ONLYA workspace key (rpe_...) was sent. Those only work under /v1/enterprise
403CSRF_FAILEDSession call without the matching X-CSRF-Token header
403ACCOUNT_INACTIVE, SUSPENDEDThe account is closed, being deleted or suspended
429RATE_LIMITEDOver the rate limit. Wait for Retry-After seconds

Get the signed-in user ​

GET/v1/auth/me

Returns the user behind the session or API key, plus the session's csrf_token (null for API keys). Signed out, it answers 200 with "data": null instead of an error, so it is safe to call on page load.

  • Auth: Optional. It works signed out; with a session or API key it also tells you whether you are a party.
  • Rate limit: 300 requests a minute per user, shared by every route.
  • Idempotency: Not needed. Reading has no side effects.

Example request ​

bash
curl https://api.rekberpay.com/v1/auth/me \
  -b cookies.txt
js
const res = await fetch('https://api.rekberpay.com/v1/auth/me', {
    headers: {
        Cookie: process.env.REKBERPAY_SESSION_COOKIE, // '__Host-rekber_session=...' from POST /v1/auth/login
    },
});
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.get(
    "https://api.rekberpay.com/v1/auth/me",
    timeout=30,
)
print(resp.status_code, resp.json())
php
<?php

$ch = curl_init('https://api.rekberpay.com/v1/auth/me');
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

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"
    }
}
FieldTypeDescription
user.idUUIDUser ID. This is the seller or buyer value in signed links
user.rolestringuser for normal accounts
user.kyc_statusstringnone, pending, verified or rejected
user.balancenumberRekberPay balance in rupiah
user.two_factor_methodstringnone or email
user.email_verification_requiredbooleantrue while an account that signed up with email and password has not confirmed its email. Escrow, payment and withdrawal writes answer 403 EMAIL_NOT_VERIFIED until then
csrf_tokenstring | nullThe session token for X-CSRF-Token. null with an API key

Errors ​

StatusCodeCause
404USER_NOT_FOUNDThe account was removed while the session was open
429RATE_LIMITEDOver the rate limit. Wait for Retry-After seconds

Update the signed-in user ​

PATCH/v1/auth/me

Changes profile fields. Send only the fields you want to change. Unknown fields are rejected.

  • Auth: A personal API key (Authorization: Bearer <key>) or a session cookie with X-CSRF-Token on writes.
  • Rate limit: 300 requests a minute per user, shared by every route.
  • Idempotency: Not supported. A retry runs the call again.

Body (JSON) ​

NameTypeRequiredDescription
full_namestringNoAt most 120 characters, no <, > or ". An empty string clears it
usernamestringNo3 to 30 characters: lowercase letters, digits, _ and .. Must be unique
phonestringNoAt most 32 characters
social_instagramstringNoInstagram handle or profile URL
social_shopeestringNoShopee shop URL
social_tiktokshopstringNoTikTok Shop URL

Example request ​

bash
curl -X PATCH https://api.rekberpay.com/v1/auth/me \
  -b cookies.txt \
  -H "X-CSRF-Token: $CSRF_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "username": "rina.wijaya",
    "phone": "081234567890"
  }'
js
const res = await fetch('https://api.rekberpay.com/v1/auth/me', {
    method: 'PATCH',
    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({
        username: "rina.wijaya",
        phone: "081234567890"
    }),
});
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.patch(
    "https://api.rekberpay.com/v1/auth/me",
    headers={
        "X-CSRF-Token": os.environ["REKBERPAY_CSRF_TOKEN"],
    },
    json={
        "username": "rina.wijaya",
        "phone": "081234567890",
    },
    timeout=30,
)
print(resp.status_code, resp.json())
php
<?php

$ch = curl_init('https://api.rekberpay.com/v1/auth/me');
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([
        'username' => 'rina.wijaya',
        'phone' => '081234567890',
    ]),
]);
$response = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
curl_close($ch);

Example response ​

200 OK

json
{
    "success": true,
    "data": {
        "user": {
            "id": "11111110-1111-4111-8111-111111111111",
            "email": "[email protected]",
            "role": "user",
            "full_name": "Rina Wijaya",
            "username": "rina.wijaya",
            "phone": "081234567890",
            "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
        }
    }
}

Errors ​

StatusCodeCause
400INVALID_BODYAn unknown field, or a field that is too long
400NO_UPDATESThe body had no fields
400INVALID_USERNAME, INVALID_FULL_NAMEThe value has characters that are not allowed
400INVALID_SOCIAL_INSTAGRAM, INVALID_SOCIAL_SHOPEE, INVALID_SOCIAL_TIKTOKSHOPThe handle or URL is not valid for that site
409USERNAME_TAKENAnother user has this username
401UNAUTHORIZED, SESSION_EXPIRED, INVALID_TOKENNo session or API key, the session expired, or the key is wrong or revoked
401WORKSPACE_KEY_ONLYA workspace key (rpe_...) was sent. Those only work under /v1/enterprise
403CSRF_FAILEDSession call without the matching X-CSRF-Token header
403ACCOUNT_INACTIVE, SUSPENDEDThe account is closed, being deleted or suspended
429RATE_LIMITEDOver the rate limit. Wait for Retry-After seconds

Get the personal API key ​

GET/v1/auth/me/api-key

Shows a masked preview of your live personal API key, when it was created and when it was last used. Every field is null when the account has no live key (none was ever created, or the last one was revoked). The full key is only shown once, when it is created. Check last_used_at before you rotate: a recent time means something still uses the key.

  • Auth: A personal API key (Authorization: Bearer <key>) or a session cookie with X-CSRF-Token on writes.
  • Rate limit: 300 requests a minute per user, shared by every route.
  • Idempotency: Not needed. Reading has no side effects.

Example request ​

bash
curl https://api.rekberpay.com/v1/auth/me/api-key \
  -b cookies.txt
js
const res = await fetch('https://api.rekberpay.com/v1/auth/me/api-key', {
    headers: {
        Cookie: process.env.REKBERPAY_SESSION_COOKIE, // '__Host-rekber_session=...' from POST /v1/auth/login
    },
});
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.get(
    "https://api.rekberpay.com/v1/auth/me/api-key",
    timeout=30,
)
print(resp.status_code, resp.json())
php
<?php

$ch = curl_init('https://api.rekberpay.com/v1/auth/me/api-key');
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

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"
    }
}
FieldTypeDescription
api_keystring | nullMasked preview of the live key: its first 6 and last 4 characters. null when there is no live key
created_atstring | nullWhen the live key was created (ISO 8601, UTC)
last_used_atstring | nullWhen the live key last authenticated a request (ISO 8601, UTC, updated at most once a minute). null when it was never used

Errors ​

StatusCodeCause
401UNAUTHORIZED, SESSION_EXPIRED, INVALID_TOKENNo session or API key, the session expired, or the key is wrong or revoked
401WORKSPACE_KEY_ONLYA workspace key (rpe_...) was sent. Those only work under /v1/enterprise
403ACCOUNT_INACTIVE, SUSPENDEDThe account is closed, being deleted or suspended
429RATE_LIMITEDOver the rate limit. Wait for Retry-After seconds

Create or rotate the personal API key ​

POST/v1/auth/me/api-key/rotate

Creates a new personal API key and revokes every older one at once. The answer holds the plaintext key. Store it now: only a hash is kept, so it cannot be shown again.

  • New keys start with rpk_. Older keys without the prefix keep working until you rotate them.
  • At most 5 rotations per account per hour. The sixth gets 429 ROTATE_LIMITED and the current 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 this call (the Generate Baru button in the dashboard) does.

The key acts as you on every route outside /v1/enterprise and is not subject to CSRF. A password reset or a password change revokes every personal API key of the account, so create a new key afterwards. A key never carries staff rights: RekberPay admin routes only accept a signed-in session. For server-to-server work on a business workspace, use a workspace API key instead.

  • Auth: A personal API key (Authorization: Bearer <key>) or a session cookie with X-CSRF-Token on writes.
  • Rate limit: 300 requests a minute per user, shared by every route.
  • Idempotency: Not supported. A retry runs the call again.

Body (JSON) ​

NameTypeRequiredDescription
labelstringNoA name for your own records, up to 64 characters: letters, digits, spaces and _ . : -, starting with a letter or digit. Defaults to default

Example request ​

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);

Example response ​

200 OK

json
{
    "success": true,
    "data": {
        "api_key": "rpk_kM3vT8qR1xZ6nB4wY9pL2sD7fH0jC5gA3eU8iO1tN6r"
    }
}

Errors ​

StatusCodeCause
400INVALID_BODYThe body is not a JSON object, or label is not a string, is empty or longer than 64 characters, does not start with a letter or digit, or has other characters
401UNAUTHORIZED, SESSION_EXPIRED, INVALID_TOKENNo session or API key, the session expired, or the key is wrong or revoked
401WORKSPACE_KEY_ONLYA workspace key (rpe_...) was sent. Those only work under /v1/enterprise
403CSRF_FAILEDSession call without the matching X-CSRF-Token header
403ACCOUNT_INACTIVE, SUSPENDEDThe account is closed, being deleted or suspended
429ROTATE_LIMITEDMore than 5 rotations in the last hour. The key was not changed. Try again after Retry-After seconds
429RATE_LIMITEDOver the rate limit. Wait for Retry-After seconds

Escrow, payment services and B2B deals for Indonesian businesses.