Skip to main content

API Overview

The PortaAIM API is a RESTful JSON API that provides full programmatic access to all platform features.

Base URL​

https://api.portaaim.ai/api/v1

Authentication​

Most API requests are authenticated using an API key passed in the X-API-Key header:

X-API-Key: your_api_key_here

An API key carries one or both roles — charging (submit usage) and admin (manage customers, products, and billing configuration) — and only works on the endpoints its role covers. API keys are scoped to a specific tenant and environment (live or sandbox), so no additional environment header is needed. Create and manage API keys from Developers → API Keys in the dashboard.

A few endpoints refuse an API key of any role and require an admin login (email/password → JWT): creating and revoking a key on the API Keys endpoint, GET /admins/me, and the session endpoints under /auth/*. See Endpoints That Require an Admin Login for the reason behind each one.

See Authentication for details.

Request Format​

  • Content-Type: application/json
  • All request bodies are JSON
  • Dates use ISO 8601 format: 2026-03-23T10:30:00Z

Response Format​

Endpoints return the resource directly as JSON — there is no wrapping envelope. For example, GET /customers/{id} returns a Customer object at the top level; list endpoints return a list-response object (PaymentListResponse, CustomerListResponse, etc.) shaped per the API ref for that resource.

Error Responses​

A failed request answers with one envelope:

{
"error": {
"code": "VALIDATION_ERROR",
"message": "An item with ID 'GPT-4-TURBO' already exists. Please use a different Item ID.",
"details": {}
}
}

message is written for a person and details carries whatever else is useful for that particular refusal. code is symbolic, but do not branch on it yet: one status can still arrive under two codes depending on which layer produced it (a 502 as either BILLING_BACKEND_ERROR or BAD_GATEWAY, for instance). Branch on the status.

A rejected request body fills details.errors with one entry per field, so you can tell the caller exactly what to change:

{
"error": {
"code": "VALIDATION_ERROR",
"message": "Input data validation error",
"details": {
"errors": [
{"loc": ["body", "amount"], "msg": "Input should be greater than 0", "type": "greater_than"}
],
"path": "/api/v1/customers/42/adjust-balance"
}
}
}

The charging endpoints answer a refusal in their own shape: see Charging.

HTTP status codes:

CodeMeaning
200Success
201Created
400The request is malformed, or breaks a rule this API checks itself
401Not signed in, or the session expired
403The credential is not accepted on this endpoint
404No such resource
409A genuine conflict: the request is valid but cannot be applied as it stands
422Your input is not acceptable. Fix the request and send it again
429Too many requests, retry later
500A fault on our side
502The billing backend could not serve the request. Safe to retry

The numeric id of a plan, service, or invoice in the path answers 422 when it cannot be an id at all (not digits, zero, a leading zero, or more than 18 digits), and the message names the parameter and the value you sent. A well-formed id with no such record answers 404. A customer segment also accepts your external_id, so only a number no customer can have answers 422 there.

Some endpoints still answer 400 for a rule they check themselves, so treat 400 and 422 alike: both mean the request needs changing, and read the message for what to change.

Pagination​

List endpoints support pagination:

GET /api/v1/customers?limit=20&offset=0
ParameterTypeDefaultDescription
limitinteger20Items per page
offsetinteger0Number of items to skip

Every list response uses the same envelope: the rows under an entity-named key (e.g. customers, invoices), plus total (rows matching the query), limit/offset (the page you requested), and has_more (whether more rows exist beyond this page).

Money & Currency​

All monetary amounts are plain decimal numbers in major units (e.g. dollars, not cents) and are expressed in your account's base currency. PortaAIM uses a single currency per account, so amounts do not carry a per-field currency code — retrieve your base currency from GET /api/v1/tenant (currency) or GET /api/v1/currencies (default_currency).

Dates & Times​

Timestamps are ISO-8601 in UTC (e.g. 2026-03-23T10:30:00Z) and use the date-time format. Calendar dates (e.g. invoice issue_date/due_date, trend points) are YYYY-MM-DD with the date format.

API Endpoints Summary​

ResourceEndpointDescription
Customers/customers/*Customer CRUD and billing
Services/services/*Service management
Items/items/*Item management
Products/products/*Plan management
Charging/charging/*Usage event submission
Events/events/*Event queries
Invoices/invoices/*Invoice management
Invoice Template/templates/invoice*The template invoices are rendered with
Payments/payments/*Payment queries
API Keys/api-keys/*Key management — creating and revoking need an admin login (JWT), not an API key
Tenants/tenants/*Tenant configuration
Analytics/analytics/*Business metrics
Reference Data/reference/*, /profile/timezones*Countries, regions and timezones a record can name