Agent access & API keys
Any Candle account can issue its own agent API keys and launch tokens headlessly, on Solana or Hood, with no partner agreement required. The keys authenticate the Headless launch API and activity reporting; market and feed reads need no key at all.
Getting access
Section titled “Getting access”A headless launch needs a wallet that can sign server-side with no human in the loop. That is only possible for a wallet whose keys Candle’s wallet infrastructure actually holds: an embedded wallet, the kind an account gets by signing up with Google, X, or email. Getting a working key takes three steps, in order:
- Get an embedded wallet. Sign up, or add a login method, with Google, X, or email. An account that already has an embedded wallet for a chain skips this step.
- Delegate agent access. From the Agent access panel on your Candle profile (
staging.candle.tv/dev/agent), grant Candle’s backend permission to sign with that embedded wallet on the account’s behalf. Delegation is per chain: an account can delegate its Solana wallet, its Hood (EVM) wallet, or both independently. It is revocable at any time from the same panel. - Enable agent features and issue a key, as below.
Delegation is re-resolved live from the wallet provider on every launch; it is never cached or trusted from an earlier check. Revoke it and the very next launch attempt fails closed (DELEGATION_REVOKED), even though the key itself stays valid and works again the moment delegation is re-approved.
Managing keys
Section titled “Managing keys”Keys are managed from the Agent access panel or directly against the API. The management endpoints accept the normal authenticated Candle session, or a device token on the three key routes below; an agent API key is never accepted here, so a leaked key cannot mint more keys.
Enable agent features
Section titled “Enable agent features”POST /api/v1/agent/enablecontent-type: application/json
{ "enabled": true }Requires a linked Solana wallet on the account. This step alone does not require delegation; delegation is checked when a key is actually issued.
Issue a key
Section titled “Issue a key”POST /api/v1/agent/keyscontent-type: application/json
{ "environment": "production" }| Field | Notes |
|---|---|
environment | production (default) or test. See test keys below. |
scopes | Optional subset of launch:write, launch:read, activity:write, swap:write. Defaults to the first three; swap:write (trading) is only ever granted when asked for explicitly, here or on a device approval screen. launch:read is enforced by the launch jobs endpoint; a key issued without it cannot poll job status. |
label | Optional display name, 1 to 64 characters. Renameable later. Without one, the dashboard derives a name from the key’s capabilities. |
expiresInDays | Optional integer, 1 to 365, converted to an absolute expiresAt at creation. Mutually exclusive with expiresAt. |
expiresAt | Optional absolute epoch ms, strictly in the future. Omit both for a key that never expires (the default). |
txLimit | Optional { "usdMicros": <positive integer>, "reset": "never" | "daily" | "weekly" | "monthly" }. See Transaction limits. |
ref | Optional referral code. Attributes the account once, best-effort, and never blocks issuance. A referred account then pays 10% less on the platform fee for every trade. |
Issuance requires agent features to be enabled and at least one chain delegated; otherwise it fails AGENT_WALLET_NOT_DELEGATED (403).
{ "success": true, "key": "cndl_live_...", "keyPrefix": "a1b2c3d4", "scopes": ["launch:write", "launch:read", "activity:write"], "environment": "production", "rateLimitPerMin": 30, "dailyLaunchCap": 5, "launchChains": ["solana"]}The plaintext key is returned only in this response. Store it immediately; it cannot be recovered later. keyPrefix identifies the key in listings and in the revoke call.
launchChains records which chains were delegated at issuance. It is a display snapshot only, not the authority checked at launch time; delegation can change at any moment, so every launch re-resolves it live.
An account may hold at most 5 active keys. Revoke an unused key before issuing a sixth; past the limit, POST /api/v1/agent/keys returns VALIDATION_FAILED.
List and revoke
Section titled “List and revoke”GET /api/v1/agent/keysLists every key the account owns, revoked keys included, with keyPrefix, scopes, environment, caps, createdAt, lastUsedAt, and revokedAt. Keys created over a device token also carry mintedByDevicePrefix, naming the device that minted them; the panel renders it as “Minted by device”. Attribution only, never authority: the field grants the device nothing.
Each row also carries a usage object (todayUsdMicros, weekUsdMicros, monthUsdMicros, lifetimeUsdMicros, days30UsdMicros, tradeCount30, launchCount30, lifetimeLaunches) beside a top-level usageSince.
DELETE /api/v1/agent/keys/:prefixRevokes one key. An unknown prefix returns 404.
Renaming, and per-key usage
Section titled “Renaming, and per-key usage”PATCH /api/v1/agent/keys/:prefixCarries exactly one of { "label" }, { "txLimit" }, or { "clearTxLimit": true } per call; sending more than one is VALIDATION_FAILED. There is deliberately no expiration field here, because expiration is fixed at creation.
GET /api/v1/agent/keys/:prefix/usage?days=NA zero-filled daily series for charting: days clamped 1 to 90 (default 30), ascending, exactly one entry per calendar day whether or not anything happened that day.
Expiration
Section titled “Expiration”A key’s expiresAt is set at creation and immutable. There is no renewal call and no PATCH field for it. Once it passes, every authenticated call with that key, on any endpoint and any scope, fails KEY_EXPIRED (401) rather than the generic UNAUTHORIZED a bad key gets. The check runs after the key’s secret is verified, so a bare prefix (which can leak through a display UI) still cannot probe a key’s expiry. Recovery is always the same: issue a new key.
Transaction limits
Section titled “Transaction limits”A key’s txLimit caps its own cumulative USD-valued trading volume over the window reset names: daily (UTC day), weekly (ISO week), monthly (UTC calendar month), or never (its whole lifetime). It is checked at POST /api/v1/trade/agent/build; exceeding it is KEY_LIMIT_REACHED (403), carrying resetsAt (epoch ms, or null for a lifetime cap, which is why only a windowed cap reports retryable: true).
This is a different mechanism from the per-asset spend limits: a spend limit caps one asset’s raw amount on a single transaction, while txLimit caps cumulative USD volume over time. A key can carry both.
Device authorization
Section titled “Device authorization”The session-based flow above assumes a browser. Agent machines usually do not have one, so the rail also supports an OAuth-style device flow: the machine asks for access, a human approves it once from any signed-in browser, and the machine ends up holding its own key-management credential. The Candle CLI drives this end to end: install it with curl -fsSL https://candle.tv/install.sh | bash (or brew install candledottv/tap/candle), then run candle auth login.
How it works:
- The device requests a code and shows a verification URL plus a short user code.
- You open the URL in a signed-in browser. The approval screen names the client and lists the exact scopes being granted before you approve; a request that names no scopes asks for all four,
swap:writeincluded, and the screen says so. - On approval the device exchanges its code for a device token (
cndl_dvc_...) and an agent API key is provisioned immediately with the approved scopes. If provisioning fails (no delegation, or the account is at its key ceiling) the token is still issued and the response says why the key was not.
A device token is deliberately narrow. It authenticates exactly three routes, the key-management ones above (POST /keys, GET /keys, DELETE /keys/:prefix), and nothing else: it cannot launch or trade (that is what the keys it mints are for), it cannot list or revoke devices, and it cannot approve another device’s login. The last two mean a stolen token can neither widen its own access nor cut off the session that could revoke it. Device tokens do not expire; revocation is the lifecycle.
Revoking a device
Section titled “Revoking a device”The Authorized devices panel on your Agent access dashboard lists every device authorization with its label and last use, and groups the API keys each device minted. Revoking a device offers to also revoke those keys; accepting revokes the device and every active key it minted in a single atomic operation, so a containment action cannot be interrupted halfway. Keys you leave in place keep working: minting is attribution, not a dependency.
Using a key
Section titled “Using a key”Send the key on every headless request as the x-api-key header:
x-api-key: cndl_live_... # production keyx-api-key: cndl_test_... # test keyTiers and caps
Section titled “Tiers and caps”Every account sits in one of four trust tiers. The tier sets rate limits, daily launch caps, image-upload limits, how many wallets can be linked (see Agent wallets & spend limits), and the platform fee on API-built transactions (see Headless launch API and Agent trading API):
| Tier | Requests/min | Launches/day | Uploads/min | Linked wallets | Fee |
|---|---|---|---|---|---|
| Free | 30 | 5 | 10 | 0 | 1% |
| Believer | 30 | 20 | 10 | 0 | 0.5% |
| Pro | 300 | 50 | 30 | 10 | 0.25% |
| Max | 600 | 1,000 | 60 | 1,000 | Free |
A key’s own row caps are set once at issuance, from whether the account’s wallet held a Believer NFT at that moment. On top of that, every account has a live tier, re-evaluated continuously; the cap actually enforced on each request is the higher of the two, so an individually raised key is never lowered by a later tier re-evaluation. The daily launch cap is per account, not per key: every launch attempt, confirmed or still in flight, consumes one slot of it, whichever key made the request.
Earning Pro
Section titled “Earning Pro”An account qualifies for Pro by staking 500,000 CNDL (as sCNDL) or holding 1,000,000 CNDL, on the account’s primary wallet, whichever comes first. This is checked live, continuously, not just at key issuance. If the balance drops below both thresholds, Pro is not lost immediately: a 48-hour grace window keeps it active, falling back to Free only if the balance is still short once the window closes.
Reaching Max
Section titled “Reaching Max”Max is sales-led, not self-serve. Contact Candle via Telegram from the /dev/agent page to request it. An admin grants Max manually, with an expiry date; it does not renew itself.
Which tier applies
Section titled “Which tier applies”Where a display value is needed (the /dev/agent dashboard, the tier endpoint below), tiers resolve in order: max > pro > believer > free. Believer resolves from the calling key’s own issuance-time cap, not a separate live check.
The staging.candle.tv/dev/agent page opens with a tier strip: the four-tier table above, the current tier highlighted, a Pro-qualification progress line for Free and Believer accounts, a grace-window banner while Pro is held open by the 48-hour window, a Max expiry line, the account’s current fee rate, and lifetime fee totals by chain and asset.
Agent tier snapshot
Section titled “Agent tier snapshot”GET /api/v1/agent/tier returns the calling account’s full tier picture in one call. It accepts either an agent key or a Privy session (dual auth), is never tier-gated, and stays available even when headless launches are disabled.
{ "success": true, "tier": "pro", "liveTier": "pro", "stakedCndl": 600000, "heldCndl": 0, "thresholds": { "minStakedCndl": 500000, "minHeldCndl": 1000000, "graceMs": 172800000 }, "grace": { "active": false, "startedAt": null }, "maxTierExpiresAt": null, "feeBps": 25, "feeTotals": [{ "chain": "solana", "quoteAsset": "sol", "feeRawSum": "9007199254740993", "count": 3 }]}| Field | Notes |
|---|---|
tier / liveTier | tier is the display tier (max > pro > believer > free); liveTier never reports believer, since that label exists only on the display tier. |
grace.startedAt | null unless grace.active is true. |
maxTierExpiresAt | null unless the account is currently Max. A lapsed or revoked grant reports null rather than a past date, so this field never has to be date-compared. |
feeBps | The account’s resolved platform fee, in basis points, on API-built transactions. |
feeTotals[].feeRawSum | A raw-unit BigInt string (lamports, wei, and so on), never a JSON number. Format it with the quote asset’s own decimals; do not coerce it with Number(). |
Test keys
Section titled “Test keys”A test-environment key authenticates as the real account, but the server scopes what it can launch: the headless endpoint rejects a test-key request whose visibility is not test or hidden (VALIDATION_FAILED, field visibility). Use a test key for integration testing so nothing lands in the production feed by accident.
Keys issued before self-funding shipped
Section titled “Keys issued before self-funding shipped”Agent launches were previously funded by Candle service wallets. That path no longer exists: a key issued before the change cannot launch again until its owner delegates at least one chain, with no grandfathering and no platform-funded fallback.
Next steps
Section titled “Next steps”- Authorize from a terminal instead of a browser: Candle CLI
- Launch headlessly: Headless launch API
- Trade on an existing market: Agent trading API
- Import or link a wallet, set spend limits: Agent wallets & spend limits
- Get pushed the outcome instead of polling: Webhooks
- Poll markets after a launch: Market state API
- A typed client for all of it: TypeScript SDK
- Use the tools from an MCP-capable agent: Candle MCP server