Getting started

Authentication

Log in for an access token and send it as a Bearer credential.

The GrivaOne API uses JWT bearer tokens. You exchange an email and password for a short-lived access token, then send that token on every subsequent request. There are no API keys or OAuth flows today — see the roadmap for what is planned.

1. Log in for an access token

POST /auth/login takes a JSON body with email and password. It is a public endpoint, but it is rate limited to 5 attempts per minute per IP — see rate limits.

curl · POST /auth/login
curl -X POST https://gulfpay-production.up.railway.app/api/v1/auth/login \
  -H "Content-Type: application/json" \
  -d '{
    "email": "you@company.com",
    "password": "your-password"
  }'

A successful login returns the caller, their tenant context and the tokens:

json · 200 OK
{
  "data": {
    "requiresOtp": false,
    "user": { "id": "usr_...", "email": "you@company.com", "name": "..." },
    "organization": { "id": "org_...", "name": "..." },
    "companies": [ { "id": "cmp_...", "name": "..." } ],
    "membership": { "role": "OWNER", "companyAccess": "ALL" },
    "accessToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
    "expiresIn": 900,
    "refreshToken": "b7c1e2...",
    "refreshExpiresAt": "2026-09-13T09:00:00.000Z"
  },
  "meta": { "requestId": "req_...", "timestamp": "2026-09-06T09:00:00.000Z" }
}
  • accessToken — the JWT you send as a bearer credential. It expires in 15 minutes (expiresIn is the lifetime in seconds).
  • refreshToken — a longer-lived token (valid 7 days) used to mint a fresh access token without re-entering credentials. It is also set as an httpOnly cookie for browser clients; server-to-server callers use the value from the body.
  • membership — the caller's role (e.g. OWNER, ADMIN) and which companies they may access.

2. Send the token on every request

Attach the access token in the Authorization header as a bearer credential. Any request to a protected route without a valid token is rejected with 401 Unauthorized.

curl · authenticated GET
curl https://gulfpay-production.up.railway.app/api/v1/auth/me \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."

Two-factor authentication

If an account has TOTP two-factor enabled (or is challenged with an emailed one-time code), POST /auth/login does not return tokens. Instead it returns a short-lived pre-auth token:

json · login requires OTP
{
  "data": {
    "requiresOtp": true,
    "preAuthToken": "eyJ...",
    "email": "you@company.com",
    "otpChannel": "totp"
  },
  "meta": { ... }
}

Complete the challenge by calling POST /auth/verify-otp with the pre-auth token as the bearer credential and the 6-digit code. On success it returns the same token payload as a normal login. A pre-auth token can only call verify-otp / resend-otp — it cannot reach any other route.

curl · POST /auth/verify-otp
curl -X POST https://gulfpay-production.up.railway.app/api/v1/auth/verify-otp \
  -H "Authorization: Bearer <PRE_AUTH_TOKEN>" \
  -H "Content-Type: application/json" \
  -d '{ "code": "123456" }'

Refreshing an access token

When the access token expires, exchange the refresh token for a new one with POST /auth/refresh. The refresh token itself is not rotated — you keep using the same one until it expires or you log out. The response contains only a new accessToken and its expiresIn.

curl · POST /auth/refresh
curl -X POST https://gulfpay-production.up.railway.app/api/v1/auth/refresh \
  -H "Content-Type: application/json" \
  -d '{ "refreshToken": "b7c1e2..." }'

POST /auth/logout revokes the refresh token / clears the cookie.

Accounts are provisioned, not self-registered

Public self-signup via the API was removed. New tenants and users are created by the platform host or from within the product. There is no endpoint to create your own organization through the public API.

Protect your credentials

Access tokens are full-access bearer credentials for the duration of their lifetime. Send them only over HTTPS, never embed them in client-side code or URLs, and store refresh tokens securely.