Getting Started

Authentication

How PimaBill authenticates people signed in to the dashboard and servers calling the API with an API key.

Every request carries a Bearer token in the Authorization header. That token is either an access token for a person signed in to your organisation, or an API key for a server.

CredentialWho uses itWhat it can do
Access token (JWT)People, through the dashboardWhat their role allows
API keyYour serversThe whole API, except managing the team and approving payouts
Authorization: Bearer <access token or API key>

Signing In

Sign in with an email and password to receive an access token and a refresh token.

curl -X POST http://localhost:4000/api/auth/login \
  -H "Content-Type: application/json" \
  -d '{
    "email": "admin@acmecorp.com",
    "password": "securepassword123"
  }'

The response holds the tokens and who you are in the organisation:

{
  "accessToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "refreshToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "role": "OWNER",
  "permissions": ["read", "billing.write", "money.move", "money.approve", "settings.developer", "settings.providers", "org.manage", "team.manage"],
  "user": { "id": "usr_...", "name": "Jane Doe", "email": "admin@acmecorp.com" },
  "tenant": { "id": "ten_...", "name": "Acme Corp", "slug": "acme-corp" }
}

A person who belongs to several organisations signs in to the one they used last; pass tenantId to choose, or switch later with POST /api/auth/switch.

Two-step sign-in

Anyone can turn on two-step sign-in in Settings → Security: scan the QR code with an authenticator app (Google Authenticator, Microsoft Authenticator, 1Password, Authy) and enter the code it shows. You get ten recovery codes, shown once; each one signs you in once if you lose your phone.

With it on, a correct password doesn't sign you in yet. The response asks for the code instead:

{ "mfaRequired": true, "mfaToken": "eyJhbGciOiJIUzI1NiIs..." }

Finish within five minutes with the six-digit code from the app, or a recovery code:

curl -X POST http://localhost:4000/api/auth/mfa/verify \
  -H "Content-Type: application/json" \
  -d '{ "mfaToken": "eyJhbGciOiJIUzI1NiIs...", "code": "123456" }'

The response is the same as a normal sign-in. Each code works once. Accepting an invitation with an existing account that uses two-step sign-in also needs mfaCode.

Owners and admins can require two-step sign-in for everyone in the organisation (Settings → Security, or PUT /api/auth/mfa/organisation with { "required": true }), once they use it themselves. Until a member turns it on, the only thing they can do is set it up: every other request is refused with 403 and "code": "MFA_SETUP_REQUIRED". While an organisation requires it, its members cannot turn it off. API keys are not affected.

Refreshing the Access Token

Access tokens expire after 1 hour. Exchange the refresh token for a new pair without signing in again.

curl -X POST http://localhost:4000/api/auth/refresh \
  -H "Content-Type: application/json" \
  -d '{
    "refreshToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
  }'
TokenLifetimeSetting
Access token1 hourJWT_EXPIRATION
Refresh token7 daysJWT_REFRESH_EXPIRATION

A refresh token stays valid until it expires, and stops working as soon as the person is deactivated or removed from the organisation.

Roles

Signed-in people are held to their role, read fresh on every request, so a role change or removal applies at once.

RoleCan
OWNEREverything. Only owners can make, change or remove other owners
ADMINEverything else: team, payment providers, organisation settings, billing and money
FINANCEBilling, collections, payouts and transfers, including approving payouts
DEVELOPERAPI keys, plus read access to everything
VIEWERRead only

API Keys

API keys are for server-to-server calls. Your organisation's first key comes back when you register; create more from the dashboard or the API.

curl -X POST http://localhost:4000/api/tenants/me/api-keys \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer sk_live_a1b2c3d4..." \
  -d '{
    "name": "Production Key",
    "scopes": ["customers:read", "customers:write"]
  }'
A key is shown in full only when it is created. Store it in an environment variable or a secrets manager; if you lose it, create a new one and delete the old one.

Scopes

Every key acts only within its scopes; a request outside them is refused with 403.

ScopeAllows
readGET requests anywhere in the API
writeEvery request (write includes read)
<resource>:readGET requests to one resource, e.g. customers:read covers /api/customers/...
<resource>:writeEvery request to one resource, e.g. invoices:write

Give each system the narrowest scopes it needs: a reporting job only needs read. Team management and payout approval always need a signed-in person, whatever the key's scopes.

Your organisation's own key (returned at registration) has full access. Keys are stored only as a hash, so the dashboard shows them masked; rotate the organisation key from Settings → API Keys or with POST /api/tenants/me/api-key/rotate, which returns the new key once and stops the old one at once.

Listing API Keys

curl http://localhost:4000/api/tenants/me/api-keys \
  -H "Authorization: Bearer sk_live_a1b2c3d4..."

Deleting API Keys

Delete a key as soon as it is compromised or no longer needed; requests with it fail immediately.

curl -X DELETE http://localhost:4000/api/tenants/me/api-keys/key_m9n8o7p6q5r4 \
  -H "Authorization: Bearer sk_live_a1b2c3d4..."

Security Best Practices

  1. Keep keys on the server. Never put an API key in browser JavaScript, a mobile app, or a link you give a customer; those call your own backend, which calls PimaBill. For the hosted customer portal, create a short-lived portal link instead (see below).
  2. Use environment variables. Never commit keys to source control.
    export PIMABILL_API_KEY="sk_live_a1b2c3d4e5f6g7h8i9j0..."
    
  3. One key per service and environment. Separate keys for development, staging and production, and per service, so one can be deleted without touching the others.
  4. Rotate keys. Create a new key, deploy it, then delete the old one.
  5. Review usage. Each key records when it was last used.
  6. Use two-step sign-in for everyone who signs in to the dashboard, and require it for the organisation once your team has it on.

Error Responses

401 Unauthorized

No credentials, or an invalid or expired token or key.

{
  "statusCode": 401,
  "message": "Invalid credentials"
}

403 Forbidden

The credentials are valid, but the person's role does not allow this.

{
  "statusCode": 403,
  "message": "Your role (viewer) cannot do this. Ask an owner or admin."
}

429 Too Many Requests

More than 100 requests in a minute from one address.

{
  "statusCode": 429,
  "message": "ThrottlerException: Too Many Requests"
}

The hosted customer portal is opened with a portal token, never an API key: an API key in a link would give the customer full access to your account. Your server asks for a link for one customer, and hands that link to the customer:

curl -X POST http://localhost:4000/api/portal/customers/{externalId}/sessions \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer sk_live_a1b2c3d4..." \
  -d '{ "expiresInMinutes": 60 }'

The response has the token, the ready-to-use url and expiresAt. A portal token works only for that customer's portal routes (their billing, invoices, invoice PDFs, payments and checkout), and for at most 7 days. Links that carry ?key= no longer work.

Copyright © 2026