Skip to main content

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.

EndpointWhy 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/meReturns 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/logoutAct 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​

PropertyDescription
Environment-scopedEach key is bound to either live or sandbox. No additional environment header is needed — the key determines the environment.
Tenant-scopedKeys only access your tenant's data. You cannot access another tenant's resources.
Role-basedKeys have specific permissions (e.g., charging, admin)
ExpirableKeys can have an optional expiration date

Creating an API Key​

  1. In the PortaAIM dashboard, go to Developers → API Keys
  2. Click Create API Key
  3. Enter a descriptive name (e.g., "Production Backend")
  4. Click Create
  5. Copy the key immediately — it won't be shown again

Or use the API: see API Keys endpoint.

caution

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.