Authentication
Most PortaAIM API requests are authenticated using API keys. Each key is bound to a specific tenant and environment, so a single header is all you need. A few endpoints always require an admin login (email/password → JWT) instead, and reject an API key even when it carries the admin role — see Endpoints That Require an Admin Login.
Endpoints That Require an Admin Login
These endpoints answer only to an admin session token (Authorization: Bearer <JWT>). Whatever roles your key carries, sending it as X-API-Key returns 403 Forbidden with the message This credential is not accepted on this endpoint.
| Endpoint | Why a key is refused |
|---|---|
POST /api-keys, DELETE /api-keys/{key_id} | A key that could create another key would defeat key expiration, and one that could revoke another could disable a colleague's. Listing keys is not on this list. |
GET /admins/me | Returns the profile of the signed-in administrator, read from the session token. A key identifies an application, not a person. |
POST /auth/change-password, POST /auth/logout | Act on the current admin session, and only a login creates one. |
That same 403 also answers other refusals of an API key — one that lacks the role an endpoint needs, or one sent with an X-Environment header contradicting the environment the key is bound to. The message is deliberately uniform, so read it as "this credential does not work here", not as proof that the endpoint is session-only.
POST /auth/login, POST /auth/set-password, POST /auth/forgot-password, and POST /auth/reset-password take no credential at all — they are how a session token is obtained in the first place. POST /auth/refresh renews an existing session, so it needs that session token (an expired one is still accepted) and never an API key.
Every other endpoint accepts an API key carrying the matching role, GET /api-keys included: the list withholds every key value, so reading it hands a key nothing it did not already hold.
Using an API Key
Include the key in the X-API-Key header on every request:
curl -X POST https://api.portaaim.ai/api/v1/charging \
-H "X-API-Key: sk_live_abc123..." \
-H "Content-Type: application/json" \
-d '{"customer_external_id": "usr_9f3k2x", "services": [{"service_code": "AI-TOKENS", "item_code": "GPT-4-TURBO", "units": 1500}]}'
Key Properties
| Property | Description |
|---|---|
| Environment-scoped | Each key is bound to either live or sandbox. No additional environment header is needed — the key determines the environment. |
| Tenant-scoped | Keys only access your tenant's data. You cannot access another tenant's resources. |
| Role-based | Keys have specific permissions (e.g., charging, admin) |
| Expirable | Keys can have an optional expiration date |
Creating an API Key
- In the PortaAIM dashboard, go to Developers → API Keys
- Click Create API Key
- Enter a descriptive name (e.g., "Production Backend")
- Click Create
- Copy the key immediately — it won't be shown again
Or use the API: see API Keys endpoint.
The full API key is only displayed once at creation time. Store it securely in your application's environment variables. If you lose it, create a new key and revoke the old one.
Live vs. Sandbox Keys
Since each API key is bound to an environment, use separate keys for development and production:
- Sandbox key — use during development and testing. All data is isolated.
- Live key — use in production. Charges affect real customer balances and invoices.
Your application should read the key from an environment variable, making it easy to switch:
import os
API_KEY = os.environ["PORTAAIM_API_KEY"] # sandbox key in dev, live key in prod
Choosing the Environment with a Session Token
A session token is not bound to an environment the way a key is, so it reaches whichever one you
name in the X-Environment header, live or sandbox:
curl https://api.portaaim.ai/api/v1/invoices \
-H "Authorization: Bearer <JWT>" \
-H "X-Environment: sandbox"
Omit the header and you get live. There is no prompt and no warning, so a request meant for
sandbox that leaves the header out acts on real customers. A value the API does not recognise is
read the same way, so spell sandbox exactly: a typo is not refused, it goes to live.
With an API key, send no environment header at all: the key's own environment is what counts, a
header naming that same environment is accepted, and one naming the other is refused with 403.
These operations take no environment header, because their answer is the same in every environment: changing your password and signing out, which act on both environments at once, and the country, region and timezone lists. The default billing timezone does depend on the environment, so it still reads the header.
Security Best Practices
- Use separate keys for different applications (backend, staging, CI)
- Store keys in environment variables — never commit them to source control
- Rotate keys periodically
- Revoke keys immediately if compromised
- Use the minimum required permissions for each key
Managing API Keys
See the API Keys endpoint reference for creating, listing, and revoking keys programmatically.