Authentication
The Phantom REST API uses opaque bearer tokens — no OAuth, no JWT, no session cookies. Mint one per integration, store it in your secret manager, and send it in the Authorization header.
Token format
phk_live_AbCdEfGhIjKlMnOpQrStUvWxYz0123456
└─┬─┘ └─┬┘ └────────────────┬───────────────┘
│ │ └── 32 random bytes (256-bit secret)
│ └── 'live' in production, 'test' in non-prod environments
└── brand prefix — `phk` = Phantom KeyThe prefix-and-tail (phk_live_AbCdEfGh…0123) is what the dashboard shows after creation. The full token is only displayed once — Phantom stores its SHA-256 hash and cannot recover the plaintext. If you lose a token, revoke and re-mint rather than trying to retrieve it.
Why prefixes? Leaked tokens are recognisable in scans. GitHub's secret scanner already understands
phk_live_*patterns; we encourage you to add it to your own pre-commit hooks too.
Sending the token
Authorization: Bearer phk_live_AbCdEfGhIjKlMnOpQrStUvWxYz0123456Anything else (basic auth, query string, body field) is rejected with 401 missing_token.
Scopes
Every token carries an explicit list of granted scopes. Every endpoint declares the scope it requires; requests without the right scope return 403 insufficient_scope. Grant the smallest set that gets the job done.
| Scope | Lets the token… |
|---|---|
guild:read | Read guild metadata + settings. |
cases:read | Read moderation cases. |
cases:write | Create and edit moderation cases. |
economy:read | Read wallets, leaderboard, ledger. |
economy:write | Adjust wallet balances. |
levelling:read | Read XP and leaderboard. |
levelling:write | Adjust XP. |
tickets:read | Read tickets and messages. |
tickets:write | Reply, claim, assign, close, reopen. |
giveaways:read | Read giveaways and entries. |
giveaways:write | Create, end, reroll. |
network:read | Look up Phantom Network hits for a user. |
webhooks:manage | Manage outbound webhook endpoints. |
There is no wildcard scope. A token that needs to do five things must list those five things.
IP allowlist
You can pin a token to one or more IPs / CIDRs. Requests from any other address fail with 403 ip_not_allowed.
203.0.113.5
198.51.100.0/24
2001:db8::/32The dashboard accepts comma- or whitespace-separated entries. Leave it blank for an unrestricted token.
Expiry
Optionally, a token can be set to expire after N days. After the expiry, requests fail with 401 invalid_token. Expiring tokens are a good fit for short-lived deploy keys and CI tokens — long-lived integration tokens should usually use the no-expiry option.
Revocation
Revocation is immediate. The auth middleware caches token lookups for 60 seconds, but revoke busts that cache the moment it runs — a request that started with a valid token mid-revoke will still complete, but the very next call will 401.
Use the Revoke button on the dashboard's API Tokens page, or DELETE /dashboard/integrations/api-tokens/{id} (this is a dashboard route, not an API route — you can't revoke API tokens with the API, by design).
Common 401/403 codes
| Code | Meaning | What to do |
|---|---|---|
missing_token | No Authorization: Bearer … header. | Send the header. |
invalid_token | Token doesn't exist, is revoked, or has expired. | Mint a new one. |
ip_not_allowed | Request came from an IP outside this token's allowlist. | Add the IP, or call from an allowed one. |
insufficient_scope | Token is missing the scope this endpoint requires. The required scope is in error.required_scope. | Grant the scope, or use a token that has it. |
https_required | The request reached us over plain HTTP. | Use https://. |
Token rotation strategy
- Mint a new token with the same scopes as the old one.
- Deploy it to your service alongside the old one (most production secret stores support staged rollouts).
- Cut over.
- Revoke the old token from the dashboard.
There is no in-place rotation — you can't change a token's secret without minting a new row. The rationale: a stable token_id would let a leaked-then-rotated token's id leak further; minting a new token forces you to actively replace it everywhere it's referenced.
Anti-patterns
- Don't put a token in a client-side app. They're plaintext in your bundle and inside the user's devtools.
- Don't share one token across multiple integrations. Use one per integration so revoking one doesn't take the others down.
- Don't grant
*:writeunless you're going to write. A leaked read-only token has dramatically less blast radius. - Don't email tokens. Use a secret manager (1Password, Vault, AWS Secrets Manager, etc.).
