Authentication
Organization API keys for the Public API, SMAO-issued bearer keys for the Distributor API.
The two APIs use two independent auth schemes — a Public API key never works against the Distributor API and vice versa. Both are sent the same way: Authorization: Bearer <key>. Newly issued Public API keys are ~43-character base64url strings with no prefix; legacy smao_-prefixed Public API keys remain valid.
Public API — organization API keys
Create keys yourself in the dashboard under Settings → API Keys → New key. Each key gets a Role that scopes what it can do — pick the narrowest role that covers your use case. Keys belong to one organization; every request is scoped to that organization's data.
curl https://api.smao.ai/api/v1/ping \
-H "Authorization: Bearer YOUR_API_KEY"A missing or invalid key returns 401:
{ "error": { "code": "unauthorized", "message": "Missing API key" } }A valid key whose role lacks permission for the endpoint returns 403 with error.code: "forbidden". See Errors for the full envelope.
The raw key is shown once, at creation. Store it in a secret manager — it cannot be retrieved later.
Key handling:
- One key per integration. Separate keys let you revoke one consumer without breaking the others.
- Rotate by overlap. Create a new key, deploy it, then delete the old one. Both work in parallel in between, so rotation needs no downtime.
- Revoke immediately on leak. Deleting a key invalidates it at once.
Distributor API — distributor keys
Distributor keys are issued by SMAO during onboarding — there is no self-service issuance. The raw key is shown exactly once at creation; the server stores a SHA-256 hash plus the first 12 characters as an identification prefix — never the full key.
Each key is issued for one Distributor API contract version. Calling the other
version returns 403 API_VERSION_NOT_ALLOWED.
v2 keys carry this fixed permission set:
organizations:readorganizations:writeusage:read
Compatibility v1 keys also carry partners:read and partners:write for the
partner-based endpoints. Permission sets are immutable after issuance.
Keys expire, by default after 365 days. Request a replacement key from SMAO before expiry and switch over — old and new keys work in parallel until the old one expires or is revoked, so rotation needs no downtime.
curl https://api.smao.ai/api/distributor/v2/organizations \
-H "Authorization: Bearer YOUR_DISTRIBUTOR_KEY"Auth failures return 401 in the Distributor error envelope, with an errorCode telling you why:
{ "status": "error", "errorCode": "INVALID_API_KEY", "message": "Invalid or revoked API key." }errorCode | Meaning |
|---|---|
MISSING_API_KEY | No Authorization: Bearer header, or an empty key. |
INVALID_API_KEY | Key unknown or revoked. |
API_KEY_EXPIRED | Key past its expiry date — request a replacement from SMAO. |
API_VERSION_NOT_ALLOWED | The key belongs to the other Distributor API version, or has invalid version metadata. |
Only GET /health is public in each version; every other Distributor endpoint
requires a version-matched key. See the v2 overview,
v2 quickstart, and
v1 quickstart.
Comparison
| Public API | Distributor API | |
|---|---|---|
| Issuance | Self-service: dashboard Settings → API Keys | By SMAO during onboarding |
| Scoping | One organization | All organizations managed by the distributor |
| Permissions | Role-based, chosen per key | Fixed per version; v1 additionally has partner permissions |
| Expiry | None | 365 days by default |
| Rotation | Create new key, deploy, delete old | Request replacement from SMAO; parallel until old key expires or is revoked |
| Rate limits | Per key defaults: read 1000/min, write 200/min, POST /calls 60/hour | Per key: 60 req/min default (configurable per key) |
| 401 shape | { "error": { "code": "unauthorized", ... } } | { "status": "error", "errorCode": "...", "message": "..." } |
For the Public API, matched operations that pass authentication and the
feature gate return X-RateLimit-Limit, X-RateLimit-Remaining, and
X-RateLimit-Reset after the limiter succeeds. Auth failures, feature-gate
failures, unmatched routes, and limiter-storage failures do not carry the full
header set. Details in Errors.