Authentication
How Arkanis API tokens work: scoping, capability inheritance, rotation, expiry, and revocation. The same token model powers REST and MCP.
What is this?
One arkpat_ token shape powers both REST and MCP. Each token is bound to one guild, carries a snapshot of the issuer's capabilities, and is re-checked live against the issuer's current capabilities on every call. You can also pin a token to an IP allowlist and see rotation reminders at 90 and 180 days.
Why you might want it
This is the reference for everything token-related: how scoping works, what the live re-check catches, when auto-revoke fires, how the 7/30/90-day expiry options compare, and when to renew vs rotate vs revoke. Reach for it whenever a token misbehaves or you want to tighten an integration's blast radius before shipping it.
What this is for
Minting an Arkanis personal access token (arkpat_) for a script, integration, CI job, or MCP client. One token shape, scoped to one guild, capability-bound to whoever issued it.
Before you start
- ·Your guild is on the Pro tier.
- ·You are the Discord guild owner (the API & MCP mint surface is owner-gated).
- ·You have flipped Developer Mode on under Settings Panel. The toggle is per-browser; do it once on the machine you mint from.
- ·You have a secret store ready (1Password, Vault, GitHub Actions secrets, etc.). The plaintext is shown once.
5-minute setup
- 1Open the dashboard for your guild → Settings → API & MCP.
- 2Click Create token on the REST or MCP tab, name it after the integration that will hold it (e.g. CI deploy bot).
- 3Pick the capabilities the integration needs. Default inherits your full set; narrow it down to the smallest workable subset.
- 4Optional but recommended for production: add a CIDR allowlist so the token only works from your CI egress or jump host.
- 5Pick an expiry (7d / 30d / 90d / never). 90d is the default and the right answer for most integrations.
- 6Copy the plaintext into your secret store now. It starts with
arkpat_and is never shown again.
Common failure modes
- Calls suddenly return CAPABILITY_DENIEDThe issuing user lost a capability or got demoted. The live re-check intersects snapshot ∩ live on every call, so a role demotion takes effect immediately. Restore the capability on the issuing user, or mint a fresh token from someone who still has it.
- Calls return TOKEN_IP_NOT_ALLOWED from a known-good sourceThe caller's public IP is not inside any CIDR on the allowlist. NAT or IPv6 dual-stack often surprises here. Check your egress IP from the call site (
curl -s https://ifconfig.me), then edit the allowlist on the token row to match the real source. - "Invalid token format" 401 on the first callThe token shape is wrong. Bearer tokens must start with
arkpat_followed by 48 chars (55 total). Confirm there's no leading whitespace, no trailing newline from a copy, and no quoting from a shell expansion. - Lost the plaintext, can't find it anywhereThere is no recovery flow by design. Revoke the row from the dashboard and mint a new token. Update the integration with the new plaintext.
One Token Model, Two Surfaces
Both the REST API and the MCP server use the same token type: an Arkanis Personal Access Token (arkpat_). One row in the api_tokens table, one hash, one audit lineage. The token carries a kind discriminator ("mcp" or "rest") but the wire format, scoping rules, and lifecycle are identical.
- Format: opaque random, prefix
arkpat_+ 48 chars Crockford base32 (no I, L, O, or U). Total length 55 chars. - Entropy: 240 bits, generated server-side.
- Storage: argon2id hash on disk. The plaintext is shown once at issuance or rotation and never again.
- Leak detection: the
arkpat_prefix is registered with secret-scanners (GitHub, gitleaks) so a leaked token can be auto-detected.
Scope: Per-Guild + Capability Snapshot
Every token is bound to a single guild for v1. Multi-guild tokens are reserved for the Operator tier and are not exposed in the dashboard yet — the schema supports them, the UI does not.
At issuance, Arkanis records two things in the token's guild scope row:
- Guild ID — the only guild this token can act in.
- Capability snapshot — the issuing user's current capability set in that guild at the moment of issuance. REST tokens may explicitly narrow the snapshot; MCP tokens take the full inheritable set.
The snapshot is the ceiling. The token can never do anything that wasn't in the issuing user's set at issuance, even if the user is granted more capabilities later. That cuts off a privilege-escalation shape where a long-lived token quietly inherits new powers behind the customer's back.
Live Re-check on Every Call
On every request, Arkanis re-resolves the issuing user's current capabilities in the token's guild and computes snapshot ∩ live. That intersection is what the call is allowed to do.
Two staleness shapes get caught for free:
- Capabilities granted after issuance — not available to the token (snapshot is the ceiling).
- Capabilities removed after issuance — not available to the token (live state is the floor). If staff revokes a role mid-day, every subsequent call from that token loses the affected capabilities immediately.
A token whose intersection is empty for the targeted guild returns CAPABILITY_DENIED on every write. Reads still work if any read capability survives the intersection.
Optional IP Allowlist
Every token may carry a per-token CIDR allowlist that restricts where the token works. A token with no allowlist is usable from any source IP (the default). A token with one or more entries is fail-closed everywhere else: calls from a non-allowlisted IP return TOKEN_IP_NOT_ALLOWED (HTTP 403) without touching the token's last-used timestamp, so a denied call looks identical to a never-issued call on the row state.
When to use it:
- A token that should only ever run from a known CI/CD egress (GitHub Actions, GitLab CI, CircleCI).
- A token used from a single jump host, bastion, or internal automation server.
- A token bound to a fixed office IP block while staff travel uses dashboard access.
Accepted entry shapes:
- IPv4 CIDR —
10.0.0.0/8,192.0.2.0/24 - IPv6 CIDR —
2001:db8::/32 - Bare IPv4 (treated as /32) —
203.0.113.5 - Bare IPv6 (treated as /128) —
2001:db8::1
IPv4-mapped IPv6 callers (::ffff:a.b.c.d — emitted by some proxies) are normalised back to IPv4 before the match, so an allowlist with 192.0.2.0/24 matches a caller arriving as ::ffff:192.0.2.10.
The allowlist is editable in place from the dashboard — open Settings → API & MCP → REST tokens, click Edit next to the IPs: row, edit the list, and save. Clearing the list (empty textarea) reverts the token to the any-IP default. The check runs after the cryptographic verify on every call; updates take effect on the next request.
Rotation Cadence (90 / 180 days)
Long-lived credentials accumulate exposure surface. Think CI logs, ex-staff laptops, shells backgrounded months ago. Arkanis nudges you to rotate on a fixed cadence so that exposure has a bounded lifetime, even if you never actively touch the token.
The two thresholds:
- 90 days since the token was last issued or rotated → the dashboard shows a dismissable soft warning on the row. Click Snooze to suppress the warning until the 180-day threshold catches up.
- 180 days → a nightly bot job stamps
rotation_required_aton the token. The dashboard then shows a non-dismissable banner, and the bot sends a Discord DM to the operator who issued the token. The token continues to work. The cadence is a reminder, not a kill switch.
The 90-day soft warning is computed client-side from the row's created_at or rotated_at timestamp; no server-side flag is set until 180 days. Snoozing the soft warning updates rotation_warning_dismissed_at; it does NOT defer the 180-day non-dismissable banner.
Auto-Revoke Triggers
Some role changes are large enough that we revoke the token outright instead of relying on the live re-check to silently empty its intersection. Auto-revoke fires on:
- Issuing user leaves the guild — immediate revoke.
- Issuing user loses the capability set the token was scoped against — revoke after a 24-hour grace period to handle transient role changes (e.g. a re-assigned role re-added the same day).
- Explicit revoke from the dashboard — immediate.
Expiry: 7 / 30 / 90 / None
Customer picks the expiry at issuance. Default is 90 days.
| Option | When to use |
|---|---|
| 7 days | Short-lived integrations, throwaway scripts, one-off backfills. |
| 30 days | Internal automation you'll touch monthly. |
| 90 days | Default. The right answer for most integrations. |
| No expiry | Long-lived production scripts where rotation is your discipline, not the platform's. |
Past expiry, every call returns TOKEN_EXPIRED. Use renew (keeps the same secret) or rotate (issues a new secret) to extend the token's life.
Lifecycle: Renew, Rotate, Revoke
Three dashboard actions cover the lifecycle. Each is its own audit event.
Extends expires_at by 90 days (or your chosen length). Same secret. Available on non-expired tokens. Use when the token is still trusted and you just want more runway.
Issues a new plaintext secret on the same token row — name, scope, capabilities, and expiry are preserved. The old secret is killed immediately by default. Optionally enable a 5-minute soft-overlap window per rotation so a running script can swap secrets without downtime.
Use rotate when a token might have leaked (log scrape, screenshot, accidental commit) but you want to keep the audit lineage and the integration alive.
Immediate kill. Subsequent calls return TOKEN_REVOKED. Use when a token is confirmed compromised, or the integration is done with.
Creating a REST Token
REST tokens are issued from the dashboard. Optimized for developer ergonomics — no device flow, no polling.
Open the dashboard panel
Sign in at dashboard.arkanis.gg, pick your guild, open the Settings Panel from the sidebar, and find the Developer Mode card. Toggle it on. API & MCP now appears in your sidebar's Dashboard section — open it and switch to the REST tokens tab.
The panel is gated to the Discord guild owner. Developer Mode is a per-browser preference, so you only need to flip it once per machine.
Click Create REST API Token
Fill the modal:
- Name — what this token is for. Shows up in audit rows.
- Guild scope — single guild for v1.
- Capabilities — defaults to your full inheritable set; narrow it down if the integration only needs a subset.
- Expiry — 7d, 30d, 90d, or never.
Copy the plaintext token
On success the dashboard shows the plaintext arkpat_... token with a copy button and a curl example pre-populated with your token. This is your only chance to copy it. Close the modal and the plaintext is gone forever; only the metadata (name, prefix, scope, expiry, last used) remains.
Creating an MCP Token (Device Flow)
MCP tokens use an OAuth device flow because the consumer is a local CLI process, not a script you control. The CLI never sees your dashboard session, and the dashboard never sees your file system.
Install the MCP server
npm install -g @arkanis/mcp-server or npx @arkanis/mcp-server pair for a one-shot pairing.
Run arkanis-mcp pair
The CLI calls POST /oauth/device/code, prints an 8-character user code (e.g. WGNX-7K3F), and shows the verification URL. The auto-detected client name (Claude Desktop, Claude Code, or generic) appears in the dashboard for context.
Approve in the dashboard
Open the URL the CLI printed (or, with Developer Mode enabled, go to API & MCP → MCP tokens in the sidebar and click Approve pairing). Sign in if needed, enter the 8-character code, review the requested scope (guild + capabilities inherited from your user), and click Authorize.
CLI receives the token and prints client config
The CLI was polling POST /oauth/device/token in the background. On approval it receives the plaintext token, writes it to~/.config/arkanis-mcp/config.json (or the platform equivalent), and prints a Claude Desktop / Claude Code config snippet to stdout for you to paste into your MCP client config.
Destructive Actions Need a Second Step
Capability checks govern what a token can do. Destructive operations (bans, strikes, long mutes, role wipes, panel deletes) require an additional re-auth window and a sentinel confirmation string.
- Re-auth window — 15 minutes (configurable). Opened from the dashboard with Approve next destructive action. One window covers every destructive call on that token until it expires or you close it.
- Sentinel confirmation — every destructive call also requires a
_confirmationfield matching a deterministic template (e.g.BAN USER 12345 IN GUILD 67890 PERMANENT). Prevents copy-paste accidents and prompt-injection misuse via MCP.
Full details, the complete list of destructive operations, and the sentinel templates live on the Destructive Actions page.
Auth-Related Error Codes
The REST API and MCP tool envelopes return these auth-related codes. Full error reference on the Rate Limits & Errors page.
| Code | Meaning |
|---|---|
| TOKEN_INVALID | Token doesn't exist or the secret doesn't match. |
| TOKEN_EXPIRED | Token is past its expires_at. Renew or rotate. |
| TOKEN_REVOKED | Token was revoked manually or auto-revoked. Create a new one. |
| CAPABILITY_DENIED | The live intersection lacks the capability this call needs. |
| RE_AUTH_REQUIRED | Destructive call outside the re-auth window. details.reauth_url points to the approval page. |