Authentication

The ScratchPower API uses OAuth2 password grant. Each integration is provisioned with:

  • A client_id + client_secret pair (the OAuth client).
  • An integration user (username + password) — the actor whose permissions, accounts, and audit trail your transactions run against.
  • An organisation code — your tenant on the platform.

Every request that isn't /v1/auth/login carries the access token in the Authorization header:

http
Authorization: Bearer eyJhbGciOi...

Endpoints

MethodPathWhat it does
POST/v1/auth/loginExchange credentials for an access token
POST/v1/auth/refreshExchange a refresh token for a fresh access token
DELETE/v1/auth/logoutAudit-log a logout event (JWTs are stateless — discard locally)
GET/v1/auth/meReturn the authenticated actor, role, organisation, and roles
GET/v1/accountsList the authenticated actor's accounts (with balance)

Login

curl
bash
curl -X POST https://api.scratchpower.com/v1/auth/login \
  -H "Content-Type: application/json" \
  -d '{
    "client_id": "your-client-id",
    "client_secret": "your-client-secret",
    "grant_type": "password",
    "identifier": "integration-user",
    "secret_pass": "your-password",
    "org_code": "YOUR_ORG",
    "source_channel": "WEB"
  }'

Response

200 OK
json
{
  "access_token": "eyJhbGciOi...",
  "access_in": 3600,
  "token_type": "bearer",
  "refresh_token": "eyJhbGciOi...",
  "scope": "read write"
}

access_in is the lifetime of the access token in seconds. Refresh before it expires to avoid forcing the user to log in again.

Refresh

curl
bash
curl -X POST https://api.scratchpower.com/v1/auth/refresh \
  -H "Content-Type: application/json" \
  -d '{
    "client_id": "your-client-id",
    "client_secret": "your-client-secret",
    "grant_type": "refresh_token",
    "refresh_token": "eyJhbGciOi..."
  }'

The response shape is identical to /login. Each refresh issues a new refresh_token — store the new one and discard the old.

Refresh tokens are single-use. If you refresh and then re-use the previous refresh token, the second call fails.

Bootstrap flow

After your first successful login, do these two reads once and cache the result in process memory for the lifetime of the access token:

  1. GET /v1/auth/me — confirms the token is valid and gives you the actor / role / organisation context.
  2. GET /v1/accounts — returns the actor's accounts. In typical single-account integrations you'll cache accounts[0].id and pass it as debited_account_id on every /v1/purchases/{validate,confirm} call.
bootstrap.js
js
const me = await sp.get("/v1/auth/me").then(r => r.json());
const accounts = await sp.get("/v1/accounts").then(r => r.json());
const debitedAccountId = accounts[0].id;   // cache for the session
Why two endpoints?

/v1/auth/me returns identity (who am I, what role, what org). /v1/accounts returns money state (balance, account category) and is the single source of truth for the debited_account_id you pass on every purchase. Splitting them lets the balance-bearing endpoint stay cacheable independent of identity calls.

Get the authenticated user

http
GET /v1/auth/me
Authorization: Bearer <access_token>
200 OK
json
{
  "actor": {
    "id": 1,
    "full_name": "Torti Ama-Njoku",
    "phone_number": "26773554420",
    "username": "torti",
    "email": "torti@scratch-power.com",
    "status": "ACTIVE"
  },
  "role": {
    "id": 1,
    "code": "ADM",
    "name": "Administrator",
    "permissions": ["ROLE_TOPUP", "ROLE_USER_CARE", "..."]
  },
  "organisation": {
    "id": 1,
    "code": "SPW",
    "name": "Scratch Power"
  },
  "roles": [
    { "id": 1, "code": "ADM", "name": "Administrator", "permissions": [...] }
  ]
}

accounts is intentionally not in this payload — call /v1/accounts instead.

Storing credentials

Treat client_secret and the integration user's password the same as any other production secret:

  • Read them from a secrets manager / environment variable at runtime.
  • Never commit them to source control.
  • Rotate them via Scratch Power support if leaked.