System tokens — mint, rotate, retention, revoke
A system token is the credential your code uses to ingest into ActivityLog. Every POST carries Authorization: Bearer al_… against the receiver.
There are two token shapes:
| Prefix | Purpose | Used at |
|---|---|---|
al_… |
Native ingest + OTLP + Langfuse | POST /messages, POST /otlp/v1/logs, POST /otlp/v1/traces, POST /api/public/ingestion |
alw_… |
Webhook receiver | POST /webhooks/{alw_…} |
This page covers al_…. For webhook tokens see 53-ingest-webhooks.md.
Mint a token
- Sidebar → Systems → click the system (or New System if you don't have one).
- On the system detail page click New Token.
- Pick a retention (see below).
- Click Generate.
- The token is shown once. Copy it now to your secret store.

The portal will never show you the token value again. We store only a one-way hash — there is no recovery path. If you lose it, revoke and mint a new one.
Retention — per token, not per tenant
Each token has its own retention window. This is the platform's key differentiator: a single system can have two tokens, one with 5-year retention (for compliance-shaped events) and one with 90-day retention (for high-volume analytics).
Allowed values:
| Days | Use case |
|---|---|
| 7 | Free-tier default; ephemeral testing |
| 30 | Short-lived dev environments |
| 90 | Pro-tier default; normal app activity |
| 180 | Half-year audit window |
| 365 | 1y — generic ingest, no specific compliance need |
| 730 | 2y |
| 1095 | 3y |
| 1825 | 5y — typical security-audit window; recommended for M365 sources |
| -1 | Never expire (Enterprise only) |
The portal dropdown shows only values your tier allows. Free is capped at 7. Pro is capped at 90 unless you've negotiated an override. Enterprise can pick any value including -1.
Changing retention after mint
Systems → → → Edit retention. The new value applies to future messages — already-stored messages keep the retention they were ingested under.

Rotation
There's no "rotate" button — rotation is mint-new + revoke-old:
- Mint a new token under the same system.
- Deploy the new token to your app(s).
- Verify ingest is flowing under the new token (Messages view shows the system + token id on each message).
- Systems → → Tokens → → Revoke.
The revoked token rejects all subsequent requests with 401 Unauthorized.

Tokens have no expiry by default. Rotate every 90 days as a habit even if there's no leak suspected; rotation cost is one redeploy + one revoke click.
Revocation
A revoked token cannot ingest. The data it ingested is not affected — only future requests fail.
To revoke: Systems → → Tokens → Revoke. There's no undo. If you revoke by accident, mint a new one.
What a token can and can't do
A system token is write-only and scoped to one system in one tenant. With a token you can:
- POST to
/messages(single + batch) for the bound system - POST to
/otlp/v1/logsand/otlp/v1/tracesfor the bound system - POST to
/api/public/ingestion(Langfuse) for the bound system - GET
/systems/meto confirm which system the token belongs to
You cannot:
- Read any messages back
- Touch any other system
- Touch any other tenant's data
- Mint, rotate, or revoke other tokens (use the portal or a user JWT)
If a token is leaked, the blast radius is one system's ingest — not the whole tenant.
Best practices
- One token per source environment. Production app gets its own token; staging gets its own; CI gets its own. When something goes wrong you can revoke just that environment.
- Long-retention token for audit-shaped events, short for analytics. Most M365 integrations use 5-year tokens. App logs typically use 90-day.
- Never embed in client-side code. Tokens are bearer credentials — anyone who sees the JS source can ingest as you. For browser/mobile clients, proxy through your backend.
- Watch the headers. Successful ingest returns
X-RateLimit-Remaining— log it. When you see it falling below 10% of capacity, you're about to hit the burst cap. - Free-tier sampling. Above the burst cap on Free, the API returns
X-AL-Sampled: trueand drops 1-in-N requests. If you see that header in logs, you're outgrowing Free.
Tier caps recap
| Capability | Free | Pro | Enterprise |
|---|---|---|---|
| Tokens per system | 2 | 50 | unlimited |
| Retention range | 1–7 days | 7–90 days | per contract |
| Steady ingest rate | 100 msg/s | 5,000 msg/s | per contract |
| Burst (10 s) | 1,000 msg/s | 50,000 msg/s | per contract |
| Sampling above burst? | yes | no | no |
Numbers from ../Pricing.md.
What's next
| Goal | Doc |
|---|---|
| POST your first message | 00-quickstart.md |
| Native ingest deep-dive (batch, dedup, duration, metadata) | 50-ingest-native.md |
| OpenTelemetry collector setup | 51-ingest-otlp.md |
| Webhook token (for Event Grid / ADO) | 53-ingest-webhooks.md |
Troubleshooting
401 Unauthorized — "invalid token".
The token is wrong, revoked, or scoped to a different environment than the host you're hitting. Check the system detail page in the portal to confirm the token still shows as Active.
429 Too Many Requests with Retry-After: N.
You've hit the burst cap. On Free, you'll also see X-AL-Sampled: true on subsequent requests — some are dropped silently. Either upgrade to Pro, or batch your POSTs.
413 Payload Too Large.
Your message body is over the tier inline cap (16 KB on Free, 256 KB on Pro). Either shrink the body (most over-the-cap payloads embed something that should be a separate Mode-2 ingest), or upgrade.
My token works on staging but fails on production.
Staging and production are separate environments with separate databases. Tokens minted on activitylog-api.betawebserver.com will not work on api.activitylog.com.