Skip to content

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 Key

The 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

http
Authorization: Bearer phk_live_AbCdEfGhIjKlMnOpQrStUvWxYz0123456

Anything 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.

ScopeLets the token…
guild:readRead guild metadata + settings.
cases:readRead moderation cases.
cases:writeCreate and edit moderation cases.
economy:readRead wallets, leaderboard, ledger.
economy:writeAdjust wallet balances.
levelling:readRead XP and leaderboard.
levelling:writeAdjust XP.
tickets:readRead tickets and messages.
tickets:writeReply, claim, assign, close, reopen.
giveaways:readRead giveaways and entries.
giveaways:writeCreate, end, reroll.
network:readLook up Phantom Network hits for a user.
webhooks:manageManage 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::/32

The 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

CodeMeaningWhat to do
missing_tokenNo Authorization: Bearer … header.Send the header.
invalid_tokenToken doesn't exist, is revoked, or has expired.Mint a new one.
ip_not_allowedRequest came from an IP outside this token's allowlist.Add the IP, or call from an allowed one.
insufficient_scopeToken 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_requiredThe request reached us over plain HTTP.Use https://.

Token rotation strategy

  1. Mint a new token with the same scopes as the old one.
  2. Deploy it to your service alongside the old one (most production secret stores support staged rollouts).
  3. Cut over.
  4. 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 *:write unless 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.).

Phantom is a product of Hydra Labs. The bot is run as a managed service; you do not need to host it yourself.