Authentication
How to authenticate requests to the Billium REST API using API keys.
All requests to the Billium API must include an API key in the x-api-key header.
x-api-key: sk_...API key types
Billium issues two types of keys. They share the same header and the same rate-limit bucket, but differ in scope.
| Prefix | Name | Scope | Use it for |
|---|---|---|---|
sk_ | Secret key | Full access to every permission the issuing user's role allows (up to all 34 permissions). | Your server. Never ship this to a browser or mobile client. |
pk_ | Public key | Exactly three permissions: invoice:create, invoice:view, product:view. Cannot read lists, cancel invoices, manage webhooks, or touch any other resource. | Browser-side or mobile integrations where exposing a key is unavoidable. |
Both keys are {prefix}_{96 base62 characters} — there is no separate _live_ / _test_ split at the API-key level today. A single merchant account has one logical key space.
There is no sandbox environment. Every API key hits production. For local development, run your own backend (backend/ in the Billium monorepo) and expose your webhook endpoint with ngrok or a similar tunnel. A hosted sandbox is on the roadmap but not yet available.
Never commit a secret key. Store it in a secrets manager or an environment variable, and rotate immediately if it leaks.
Obtaining an API key
- Log in to the Billium dashboard.
- Go to Settings → Developer → API keys.
- Click Create API Key, choose
SecretorPublic, and select the permissions you need. - Copy the key — it is only shown once.
Including the key in requests
HTTP
curl https://api.billium.to/api/v1/merchants/merchant/$BILLIUM_MERCHANT_ID/invoices \
-H "x-api-key: sk_..."Every merchant-scoped endpoint lives under /api/v1/merchants/merchant/{merchantId}/..., so you always need both your API key and your merchant ID.
Node.js SDK
import { Billium } from '@billium/node';
const billium = new Billium({
apiKey: process.env.BILLIUM_API_KEY,
merchantId: process.env.BILLIUM_MERCHANT_ID,
});
const invoice = await billium.invoices.create({ /* ... */ });When you pass a pk_* key, the SDK short-circuits any method that requires a secret key (invoices.cancel, webhooks.create, etc.) with a clear BilliumError instead of letting the request round-trip to a 403.
Permissions
Billium has 34 granular permissions grouped into 10 domains. Each permission gates a specific controller action — requests that exceed a key's (or a user's) permissions return 403 Forbidden.
| Domain | Permissions |
|---|---|
| Merchant | merchant:view, merchant:edit, merchant:delete, merchant:billing |
| Invoice | invoice:view, invoice:create, invoice:edit, invoice:delete |
| Product | product:view, product:create, product:edit, product:delete |
| Customer | customer:view, customer:edit, customer:delete |
| Team | team:view, team:invite, team:remove, team:manage_roles |
| API key | api_key:view, api_key:create, api_key:edit, api_key:delete |
| Webhook | webhook:view, webhook:create, webhook:edit, webhook:delete |
| Checkout | checkout:view, checkout:edit |
| Wallet | wallet:view, wallet:create, wallet:edit, wallet:delete |
| Report | report:view |
Roles
Dashboard users belong to a team for each merchant they can access, with one of four roles. The role determines the maximum set of permissions they can be granted:
| Role | Permissions | Notes |
|---|---|---|
OWNER | 34 (all) | One per merchant. Can delete the merchant, manage billing, transfer ownership. |
ADMIN | 32 | Everything except merchant:delete and merchant:billing. |
MEMBER | 15 | Read + create/edit on core resources (invoice, product, customer, wallet), view-only on team/keys/webhooks. No deletes. |
VIEWER | 10 | One *:view per domain. Read-only across the board. |
API key permissions
A secret key inherits the permissions of the user that created it, capped by the key type's maximum. Public keys are hard-capped at the minimum surface needed to run a browser-side checkout:
| Key type | Max permissions |
|---|---|
sk_ (secret) | Up to 34 (inherits the user's role cap) |
pk_ (public) | Exactly 3: invoice:create, invoice:view, product:view |
When you create a key, the dashboard lets you further narrow the set — e.g., a build server that only needs to create invoices can be issued an sk_ with just invoice:create, reducing blast radius if the key leaks.
Team-level security enforcement
Beyond per-user permissions, an OWNER can enforce team-wide security requirements (enforceTfa, enforcePasskey, enforceGoogleLogin, enforceGithubLogin, enforceEmailVerified). If an authenticated user hits an endpoint while missing a required factor, the request returns 403 with error code SECURITY_REQUIREMENT_NOT_MET and a missing[] array listing which factors to enable. API-key requests are not subject to this check — only dashboard JWT sessions.
Error responses
See Errors for the full envelope.
| Status | Cause |
|---|---|
401 Unauthorized | x-api-key header is missing, malformed, or the key is revoked |
403 Forbidden | Key is valid but lacks the required permission, or a pk_* key tried to reach an sk_*-only endpoint |
429 Too Many Requests | Key-scoped rate limit hit — see Rate limits |
Security best practices
- Store keys in environment variables or a secrets manager, never in source code.
- Create one key per integration — it makes rotation painless.
- Use public keys (
pk_*) for anything that ships to an untrusted client. - Revoke immediately from the dashboard if a key is exposed.