Authentication

Cloven supports two distinct authentication paths. They cover every deployment shape we have seen: humans clicking through a console, server-side agents pulling fresh context, and fully autonomous agents that no human ever logs into.

Mode 1 — Bearer API keys (credit-balance path)

A user-issued API key tied to a Supabase account and a prepaid credit balance. Use this when there is a human owner, when you want a dashboard with usage charts and key rotation, and when you want predictable cost from bulk credit packs.

Issuance

Open /console, claim your anonymous session with magic link or wallet, and click "New key". The key is shown exactly once, prefixed with cv_, then hashed with SHA-256 server-side. Cloven stores only the hash. If you lose the key, you must revoke and re-issue.

cv_<32 bytes of base62>

The first 16 characters form the public keyId — safe to log. The rest is the secret. Cloven never displays the full key again after issuance.

Sending the key

All REST and HTTP MCP endpoints accept a standard Bearer header:

GET /v1/fresh?pack=crypto HTTP/1.1
Host: api.cloven.cloud
Authorization: Bearer cv_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

For stdio MCP (Claude Desktop, Cursor), put the key in the env block of your claude_desktop_config.json:

{
  "mcpServers": {
    "cloven": {
      "command": "npx",
      "args": ["-y", "@cloven/mcp"],
      "env": { "CLOVEN_API_KEY": "cv_xxx" }
    }
  }
}

The SDK reads the same env var by default, or accepts apiKey as a constructor argument.

Validation flow

On every authenticated request the middleware:

  1. Extracts the Bearer token, hashes it with SHA-256.
  2. Looks up the hash in the api_keys table (RLS-bypassing service-role read).
  3. Checks revoked_at is null.
  4. Returns an ApiKeyContext with keyId, userId, tier, creditBalance, and the list of pack ids the key is subscribed to.
  5. Hands off to the rate-limiter (free-tier: 100 calls/day sliding window in Redis) then to the credit debit gate (paid calls: decrements credit_balance via the debit_credits Postgres fn).

Invalid bearer → 401 invalid_token. Revoked key → 401 key_revoked. Quota exhausted → 429 quota_exhausted with Retry-After header. Insufficient credits → 402 insufficient_credits.

Mode 2 — x402 anonymous (agent-to-agent path)

Use this when an autonomous agent needs context and no human is in the loop to provision a key. The agent signs a USDG transfer on Robinhood Chain for the per-call price; Cloven verifies the transaction and serves the response. No account, no dashboard, no monthly bill.

Flow at a glance

  1. Agent calls GET /v1/fresh?pack=crypto with no auth header.
  2. Cloven responds 402 Payment Required with PaymentRequirements (network, asset, recipient, amount, validUntil, nonce).
  3. Agent signs a USDG transfer on Robinhood Chain for the quoted amount.
  4. Agent retries the call with x-402-payment: <base64-encoded payment proof>.
  5. Cloven verifies the tx on-chain, caches the tx hash for 24h idempotency, and returns the MindResponse.

The wallet needs USDG for the payment and a small amount of ETH for gas — ETH is Robinhood Chain's native token, not USDG.

This is a Cloven-native on-chain-receipt scheme: your agent broadcasts its own transfer and Cloven self-verifies the receipt. There is no hosted facilitator and no gasless settlement path today.

Full wire format and verification rules: x402 protocol guide.

The SDK handles this dance automatically when constructed with a viem WalletClient instead of an API key — see the SDK x402 mode page.

Mode 3 — SSE token (subscription-only)

The EventSource browser API cannot set custom request headers. SSE subscriptions therefore accept the API key as a token query parameter:

GET /v1/subscribe?pack=crypto&token=cv_xxx

The token is validated identically to a Bearer header (same hash, same RLS, same rate-limit consume). Use the same value you would pass as Authorization: Bearer …. Treat the URL as secret; SSE tokens leak through server logs and HTTP proxies if not handled carefully.

x402 is not currently supported on SSE because we cannot prompt mid-stream for a fresh payment.

Credit deposits via USDG on Robinhood Chain

To fund a key's credit balance, open /console/credits in the console and select a pack (Starter / Hobby / Pro / Team). The UI creates a deposit intent and presents:

  • The exact USDG amount to send and the Cloven treasury address on Robinhood Chain.
  • A QR code encoding the EIP-681 payment URI (chain id 4663, USDG contract 0x5fc5360D0400a0Fd4f2af552ADD042D716F1d168) for wallet-app scanning.
  • An expires-in countdown (30-minute window).

Send the USDG transfer from any wallet configured for Robinhood Chain. Once you have a tx hash, paste it into the "Submit tx hash" field for an immediate fast-path verify — or wait up to 60 seconds for the background cron to pick it up automatically.

Adding Robinhood Chain to your wallet

Robinhood Chain is an Arbitrum Orbit L2 and is not preloaded in most wallets. Add it manually:

Network name  Robinhood Chain
Chain ID      4663
RPC URL       https://rpc.mainnet.chain.robinhood.com
Currency      ETH
Explorer      https://robinhoodchain.blockscout.com

Then add USDG as a custom token at 0x5fc5360D0400a0Fd4f2af552ADD042D716F1d168 (6 decimals). The testnet chain id is 46630 if you're wiring against a staging deployment.

On verification the deposits row flips to status = verified and your api_keys.credit_balance is incremented atomically. Credits are shared across all your non-revoked keys.

Deposits that receive no matching on-chain transfer within 30 minutes flip to status = expired. No charge applies. Re-open /console/credits to start a new deposit.

Tier-gated pack access

A key's packIds array determines which packs it can call. Free tier is restricted to ["crypto"]. Keys with a credit balance unlock every registered pack. Calling ?pack=ai from a free-tier key with no credit balance returns:

{
  "error": {
    "code": "pack_access_denied",
    "message": "your key does not have access to pack 'ai'; buy a credit pack to unlock all packs",
    "requestId": "req_abc123"
  }
}

Pack access is gated on credit balance, not a separate subscription toggle.

Credit balance and revocation

Revoking a key does not destroy its remaining credits — the credits stay on the row. If you re-issue a replacement key, transfer the balance manually via ops@cloven.cloud or buy a new pack on the replacement key.

Rotation + revocation

Revoke a key from /console. Revoked keys are rejected instantly (Redis cache invalidates within 60 seconds; Postgres lookup catches the rest). Issue a replacement before revoking if you care about uptime. Keys cannot be edited or "rotated" — the only safe rotation pattern is issue-new, deploy, revoke-old.