Agent authentication (agent-v1)¶
Crank serves one tool catalog over several MCP surfaces. Reads are free and
anonymous everywhere; value-bearing calls belong on agent-v1, the
authenticated agent surface, gated by a scoped, expiring, revocable pairing
token. Any MCP-capable harness can pair -- OpenAI Agents SDK, LangGraph,
CrewAI, Cursor, Codex, Gemini CLI, Claude Code, plain curl. No plugin
required.
The surfaces¶
| Surface | URL | Auth | What it serves |
|---|---|---|---|
| Public | https://mcp.crank.ing/mcp |
none | The anonymous default. Free reads, quotes, discovery. Use it read-only. |
| agent-v1 | https://mcp.crank.ing/mcp/agent-v1 |
Authorization: Bearer cpat_... or cak_... |
The autonomous-agent surface: the full catalog minus admin/support keep-outs. Every call needs a valid pairing token or API key whose scopes cover the tool. |
| agent-lite | https://mcp.crank.ing/mcp/agent-lite |
Authorization: Bearer cpat_... or cak_... |
A frozen ~14-tool subset of agent-v1 with compact schemas, for small local models. Same token. |
| plugin-v1 | https://mcp.crank.ing/mcp/plugin-v1 |
Authorization: Bearer cpat_... |
The curated Claude Code / Cowork plugin surface (narrow read/quote/intent set). Installed via the Crank plugin, not hand-wired. |
| chatgpt-v1 | https://mcp.crank.ing/mcp/chatgpt-v1 |
none | The ChatGPT Apps directory surface: anonymous, read-only, tools-only. |
The token is a pairing token (cpat_ prefix) or an API key you
issued yourself from Settings (cak_ prefix, see
API keys). Both are the same kind of credential: it
identifies your agent acting for your wallet. It is never a wallet key or
seed phrase, and no Crank code path transmits, stores, or logs private keys
(non-custodial).
Pairing: mint a token from any harness¶
Pairing is a device-code flow: the CLI prints a short user code and a verification URL; you approve in your own browser with your wallet connected; the CLI receives the token and stores it -- the secret is never printed.
Pairing CLI¶
Status: Coming soon
The create-crank-agent and crank-sdk packages are not yet published to npm
or PyPI, so the commands below do not install from a registry today. Until
they ship, pair over plain HTTP as shown in
Pairing over HTTP.
# Node (any machine with npx):
npx create-crank-agent pair --scopes read,quote,trade
# Python (ships with the crank-sdk package):
python -m crank_sdk pair --scopes read,quote,trade
What happens:
- The CLI calls
POST https://api.crank.ing/api/v1/pair/start/and prints theuser_code+ verification URL (https://crank.ing/pair?...). - You open the URL, connect your wallet, check the code matches, and approve. A request carrying any execution scope requires this browser confirmation -- it is the Lane-2 ceremony that makes execution scopes mintable at all, and it cannot be skipped or automated.
- The CLI's poll receives the token and stores it in the OS keychain
(macOS; written over stdin so the secret never appears in the process
table), falling back to a mode-0600 file at
~/.crank/agent-credentials. Metadata (wallet, scopes, surface URL) lives in that file; the token value itself stays in the keychain when available.
Inspect or retrieve the stored pairing:
npx create-crank-agent status # wallet, scopes, storage -- never the secret
export CRANK_AGENT_TOKEN="$(npx create-crank-agent token)" # explicit retrieval
token is the one deliberate retrieval path (same contract as
gh auth token) for wiring the credential into an env var, an MCP client's
headers, or a curl call. Avoid running it inside logged or recorded sessions.
Pairing over HTTP¶
This works today and needs only curl and jq. It is the same two calls the
CLI makes:
# 1. Start a pairing. Keep device_code private; show user_code to yourself.
START="$(curl -s -X POST https://api.crank.ing/api/v1/pair/start/ \
-H "Content-Type: application/json" \
-d '{"requested_scopes":["read","quote","trade"]}')"
echo "$START" | jq -r '"user code: \(.user_code)\nopen: \(.verification_url_complete)"'
DEVICE_CODE="$(echo "$START" | jq -r .device_code)"
# 2. Approve in your browser with your wallet connected. Meanwhile poll every
# `interval` seconds. The token is delivered EXACTLY ONCE, in the first
# "approved" response, so capture the whole response and read the token
# from it -- polling again afterwards returns "expired".
INTERVAL="$(echo "$START" | jq -r '.interval // 5')"
STATUS="pending"
while [ "$STATUS" = "pending" ]; do
sleep "$INTERVAL"
RESP="$(curl -s -X POST https://api.crank.ing/api/v1/pair/poll/ \
-H "Content-Type: application/json" \
-d "{\"device_code\":\"$DEVICE_CODE\"}")"
STATUS="$(printf %s "$RESP" | jq -r '.status // "pending"')"
done
if [ "$STATUS" = "approved" ]; then
export CRANK_AGENT_TOKEN="$(printf %s "$RESP" | jq -r .token)"
unset RESP
echo "paired: token is in CRANK_AGENT_TOKEN (not printed)"
else
echo "pairing did not complete: $STATUS"
fi
The cpat_ token now lives only in the CRANK_AGENT_TOKEN variable of this
shell; store it as you would any other credential. Execution scopes still
require the browser confirmation above.
Scopes¶
Request scopes at pair time with --scopes. Omit the flag for the base
read,quote,intent set. A token's scopes are a hard boundary checked on every
call: a tool whose required scope the token does not carry is refused, and
every unmapped tool defaults to read (fail-safe -- every write is mapped).
| Scope | Class | Covers | Tools mapped |
|---|---|---|---|
read |
base | Reads: balances, markets, intelligence, backtests, status. The default for any unmapped tool. | every tool not listed below (the fail-safe default), plus: kill_wallet, pause_wallet |
quote |
base | Quoting: swap and equity route quotes. No execution. | equity_quote, get_quotes |
intent |
base | Propose-only execution intents, signed later in the owner's own browser wallet. | create_execution_intent, get_intent_status |
manage |
browser-confirmed | Control-plane writes that move no funds: charter, webhooks, alerts, agent registry. | declare_jurisdiction, delete_webhook, journal_append, propose_template_update, register_agent, register_webhook, report_registry_install, set_liquidation_alert, set_portfolio_charter, sr_configure_strategy, update_agent_profile |
trade |
execution (browser-confirmed, hard expiry) | Spot + yield execution, social sends, funding on/off-ramp and bridging. | bridge_in, bridge_out, bulk_send_social, claim_ilo_tokens, evm_swap, flash_loan, fund_wallet, jupiter_swap, lend_borrow, lend_deposit, lend_repay, liquid_stake, lst_swap, offramp_to_fiat, pledge_to_ilo, send_token_social, unstake_lst |
strategy |
execution (browser-confirmed, hard expiry) | Strategy lifecycle: create, modify, pause, resume, cancel, publish, clone. | clone_strategy, compose_strategy, intelligence_suggestion_approve, publish_strategy, strategy_arb_create, strategy_basis_trade_create, strategy_cancel, strategy_composite_create, strategy_copy_wallet_create, strategy_dca_create, strategy_equity_dca_create, strategy_hedge_create, strategy_market_make_create, strategy_modify, strategy_momentum_create, strategy_pause, strategy_perp_grid_create, strategy_protect_create, strategy_rebalance_create, strategy_resume, strategy_sentiment_create, strategy_snipe_create, strategy_stoploss_create, strategy_vault_create, strategy_yield_farm_create |
perps |
execution (browser-confirmed, hard expiry) | Derivatives and synthetic short/leverage positions. | cancel_perp_order, close_perp_position, leverage_close, leverage_long, perp_close, perp_modify, perp_open_long, perp_open_short, place_perp_order, set_perp_tpsl, short_close, short_open |
equity |
execution (browser-confirmed, hard expiry) | Tokenized-equity execution (independently geo-gated). | trade_equity |
wallet_admin |
execution (browser-confirmed, hard expiry) | Policy and signer administration: wallet policy, trading limits, session signers. | approve_proposal, authorize_session_signer, clear_agnta_grant, create_agent_wallet, poll_agnta_grant_clear, poll_agnta_identity_registration, poll_permission_mode_change, poll_session_signer_authorization, reconcile_turnkey_policy, register_counterparty_set, request_agnta_grant_clear, request_agnta_identity_registration, request_permission_mode_change, request_session_signer_authorization, resume_wallet, revoke_session_signer, set_agnta_grant, set_permission_mode, set_trading_limits, set_wallet_policy, submit_agnta_identity_registration |
Using the token¶
Send it as a standard bearer header against the agent-v1 URL:
TOKEN="$CRANK_AGENT_TOKEN" # from the pairing CLI `token` subcommand or the HTTP poll
curl -sN -X POST https://mcp.crank.ing/mcp/agent-v1 \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-H "Authorization: Bearer $TOKEN" \
-d '{"jsonrpc":"2.0","id":1,"method":"initialize",
"params":{"protocolVersion":"2025-06-18","capabilities":{},
"clientInfo":{"name":"curl","version":"0"}}}'
# Reuse the returned mcp-session-id header on subsequent tools/list +
# tools/call POSTs, each carrying the same Authorization header.
Status: Coming soon
The first-party SDKs (not yet published to npm or PyPI) route to agent-v1 by
default whenever a token is
present (CRANK_AGENT_TOKEN, the token option, or -- in Python with
surface="agent" -- the stored pairing):
import { CrankClient } from "@crank-ing/sdk";
const crank = new CrankClient({ token: process.env.CRANK_AGENT_TOKEN });
// crank.mcpUrl === "https://mcp.crank.ing/mcp/agent-v1"
from crank_sdk import CrankClient
crank = CrankClient(surface="agent") # token from env or the stored pairing
Keep the anonymous public surface for read-only use: construct the client with
no token (or surface="public") and it talks to https://mcp.crank.ing/mcp
exactly as before.
401 means re-pair¶
A tools/call on agent-v1 with a missing, expired, or revoked token returns
HTTP 401 with a WWW-Authenticate header. Both SDKs raise a typed
AuthError (a subclass of CrankError) carrying the re-pair hint. A valid
token with an insufficient scope is different: that is an in-band tool
error (the call is well-authenticated but not authorized), and re-pairing with
the right --scopes is the fix.
Expiry and revocation¶
- Expiry. A token carrying any execution scope has a hard expiry (90 days). Re-pair to rotate it -- expiry is by design, not a fault. Base read/quote/intent tokens follow the standard pairing lifetime.
- Revocation. Review, rotate and revoke paired tokens and API keys any time from the Settings page at crank.ing -- revocation takes effect on the next call. A revoked token yields HTTP 401 everywhere.
- Guardrails still apply. The token authorizes the caller; the paired
wallet's own server-enforced guardrails (
WalletPolicy, kill switch, permission mode) and jurisdiction gates still run on every call, before any technology service fee.
API keys (cak_)¶
An API key is the same scoped, revocable credential as a pairing token, issued directly by you instead of through the pairing handshake -- the natural fit for a server-side integration or an agent you run yourself. It works everywhere a pairing token works (agent-v1, agent-lite, the REST agent bridge) and is listed, rotated and revoked from the same Settings page.
- Issue it. Sign in at crank.ing/settings
with your wallet, open Agent access, give the key a label and pick its
scopes.
readis always included. Base scopes (read,quote,intent) mint immediately. Any execution scope (trade,strategy,perps,equity,wallet_admin) ormanagerequires the same explicit confirmation as the pairing approval page; execution keys carry the 90-day hard expiry. - Shown once. The raw key (
cak_...) is displayed exactly once, at issuance. Crank stores only its SHA-256 hash and can never show it again -- copy it into your agent's secret store immediately. Lost it? Rotate. - Rotate. Rotate issues a replacement with the same label, scopes and rate tier and revokes the old key in the same step. The old key is refused (HTTP 401) on its next call.
- Revoke. Takes effect on the next call, on every surface.
- Audit. Every lifecycle edge -- issued, rotated, revoked, and each refused presentation of a revoked or expired key -- is recorded and visible to you as the key's owner.
- Rate tier. Keys use the per-credential
pairedtier by default; Crank staff can grant anelevatedtier. See rate limits. - Owner-bound. A key acts only for its owner's wallet: it authorizes API and MCP calls, unsigned-transaction construction and intents. Signing always stays in your own wallet -- a key never signs anything.
Use it exactly like a pairing token:
curl -sN -X POST https://mcp.crank.ing/mcp/agent-v1 \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-H "Authorization: Bearer $CRANK_API_KEY" \
-d '{"jsonrpc":"2.0","id":1,"method":"initialize",
"params":{"protocolVersion":"2025-06-18","capabilities":{},
"clientInfo":{"name":"curl","version":"0"}}}'
In the SDKs, a cak_ value in apiKey / api_key (or the CRANK_API_KEY
environment variable) is treated as the agent credential and routes to
agent-v1, just like a pairing token. A pairing token passed explicitly still
takes precedence.
Browser-based agents. The MCP HTTP surfaces answer CORS with any origin
(*) and never allow credentials, because authentication is the explicit
Authorization header, not a cookie. Operators fronting a known origin set can
restrict origins with MCP_CORS_ORIGINS (comma-separated). Allowed request
headers are Authorization, Content-Type, Accept, Mcp-Session-Id,
Mcp-Protocol-Version, X-PAYMENT, Last-Event-ID, X-Crank-Toolsets and
X-Crank-Profile; Mcp-Session-Id is exposed on responses.
Security scheme names. Where Crank's machine-readable descriptions
declare security schemes, these credentials are named crankAgentToken (HTTP
bearer: a cpat_ pairing token or cak_ API key) and x402Payment (API key
in the X-PAYMENT header).
Which surface should my integration use?¶
- Autonomous agent on any framework -> pair, then
agent-v1. - Small local model (3-8B) -> pair, then
agent-lite. - Read-only research/quotes -> anonymous
/mcp, no credential. - Claude Code / Cowork with the Crank plugin -> the plugin manages
plugin-v1pairing itself (/crank:start). - ChatGPT Apps ->
chatgpt-v1is wired by the directory listing.
See also: Authentication & x402 for wallet binding and the payment rail, and the Integration guide.