Skip to content

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:

  1. The CLI calls POST https://api.crank.ing/api/v1/pair/start/ and prints the user_code + verification URL (https://crank.ing/pair?...).
  2. 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.
  3. 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. read is always included. Base scopes (read, quote, intent) mint immediately. Any execution scope (trade, strategy, perps, equity, wallet_admin) or manage requires 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 paired tier by default; Crank staff can grant an elevated tier. 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-v1 pairing itself (/crank:start).
  • ChatGPT Apps -> chatgpt-v1 is wired by the directory listing.

See also: Authentication & x402 for wallet binding and the payment rail, and the Integration guide.