# Targe — LLM Token Sharing on Base # Version: 3.11.0 # https://api.targe.io ## What is Targe? Targe is an LLM token sharing platform for AI agents. Two use cases: 1. **Earn USDC** — Share your Anthropic, OpenAI, or Google API key. Earn 90% of every token consumed through your key. The gateway handles routing, encryption, rate limiting, and settlement. 2. **Consume pooled AI** — Access shared LLM keys from the network. Pay per token in USDC via prepaid balance. No API key needed. No subscription. - Network: Base Mainnet (eip155:8453) - Chain ID: 8453 - Currency: USDC (6 decimals) - USDC contract (Base Mainnet): 0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913 - Targe payment address: 0xBf9D8c0f5c25b7Dea1261333bb341f72bc1244E0 - Supported providers: Anthropic, OpenAI, Google - Canonical model list: always use `GET /catalog` for current models (the table in this doc is illustrative only) --- ## Quick Start — Consume Pooled LLM Capacity ### Step 1: Register your wallet Sign challenge: `targe-provider:register:{timestamp}` with your wallet (EIP-191). ``` POST https://api.targe.io/agent/register Content-Type: application/json { "address": "0xYourWalletAddress", "signature": "0xSignedChallenge", "timestamp": 1706356800000 } ``` Response: `{ "success": true, "address": "0x...", "depositAddress": "0x..." }` > **Re-registration is safe.** Calling `POST /agent/register` with the same wallet address is idempotent — it returns the same `depositAddress` and does not reset your balance. Agents that call register on every boot will not cause issues. > **Don't have a wallet?** See [Agent Wallet Generation](#agent-wallet-generation-ethersjs) below for how to create one programmatically. ### Step 2: Deposit USDC on Base Send USDC (Base Mainnet, minimum $1.00) to the `depositAddress` returned above. Balance is credited automatically after ~3 confirmations. Check balance: ``` GET https://api.targe.io/agent/balance X-Agent-Auth: eyJhZGRyZXNzIjoiMHg... <-- base64-encoded auth JSON (see Authentication section) ``` Response: ```json { "success": true, "address": "0x...", "balance": "4.250000", "reputationScore": 100, "pendingDeposits": [], "stats": { "totalDeposited": "10.000000", "totalEarned": "0.000000", "totalSpent": "5.750000" }, "earningsSpendable": true, "depositAddress": "0x..." } ``` `balance` is in USDC (6 decimal string). `pendingDeposits` are deposits awaiting on-chain confirmation. `earningsSpendable: true` means your pool key earnings are credited to this balance immediately after each request — no withdrawal required. You can earn from one key and consume from another in the same session. ### Step 3: Browse the pool catalog ``` GET https://api.targe.io/catalog ``` Response includes `keyPools[]` with provider, model, blended pricing, pool size, and utilization. **Always use this endpoint as the canonical source of available models** — the Supported Models table in this doc is illustrative and may be stale. Minimal `keyPools[]` entry shape: ```json { "id": "pool-anthropic-claude-haiku-4-5-20251001", "llmProvider": "anthropic", "model": "claude-haiku-4-5-20251001", "poolSize": 3, "blendedPricePer1MInput": "0.3000", "blendedPricePer1MOutput": "1.5000", "cheapestPricePer1MInput": "0.2500", "retailPricePer1MInput": "1.00", "retailPricePer1MOutput": "5.00", "savingsVsRetail": "70%", "poolUtilizationPercent": 22 } ``` ### Step 4: Call the pool proxy ``` POST https://api.targe.io/proxy/pool/anthropic/v1/messages Payment-Signature: eyJmcm9tIjoiMHg... <-- base64-encoded payment JSON (see Payment-Signature section) Content-Type: application/json { "model": "claude-haiku-4-5-20251001", "max_tokens": 1024, "messages": [{ "role": "user", "content": "Hello" }] } ``` The gateway selects the best available key. Response includes upstream LLM output plus `_x402_pool` metadata. For streaming: add `"stream": true` to the body. Response is SSE (`text/event-stream`). Balance settled after stream ends. --- ## Quick Start — Share Your LLM Key and Earn ### Step 1: Register your wallet (same as consumer step 1 above) ### Step 2: Register your API key ``` POST https://api.targe.io/agent/keys X-Agent-Auth: eyJhZGRyZXNzIjoiMHg... <-- base64({"address":"0x...","signature":"0x...","timestamp":...,"action":"register-shared-key"}) Content-Type: application/json { "llmProvider": "anthropic", "baseUrl": "https://api.anthropic.com", "apiKey": "sk-ant-...", "models": ["claude-haiku-4-5-20251001"], "rateLimits": { "tpm": 100000, "rpm": 60 }, "pricing": { "mode": "cost_plus", "markupPercent": 20 }, "costBasis": "subscription" } ``` > **`baseUrl` defaults:** `anthropic` → `https://api.anthropic.com`, `openai` → `https://api.openai.com`, `google` → `https://generativelanguage.googleapis.com`. Only override for Azure OpenAI or custom proxy endpoints. This field is required — there are no server-side defaults. Response: ```json { "key": { "id": "skey-abc123", "status": "available" }, "estimatedMargins": { "claude-haiku-4-5-20251001": { "ownerProfit": "8.0%", "consumerCost": "$0.30/$1.50 per M tokens (input/output)" } } } ``` Key status values: `available`, `paused`, `suspended` (3-strike auto-disable), `reclaimed`. Your key is encrypted (AES-256-GCM) and never exposed to consumers. > **Need an API key?** If you are an AI agent, prompt your operator to provide one. See [Notes for AI Agents](#notes-for-ai-agents) for the recommended flow. ### Step 3: Monitor earnings ``` GET https://api.targe.io/agent/keys/skey-abc123/earnings X-Agent-Auth: eyJhZGRyZXNzIjoiMHg... <-- base64({"address":"0x...","signature":"0x...","timestamp":...,"action":"view-key-earnings"}) ``` Response: ```json { "success": true, "keyId": "skey-abc123", "earnings": { "totalRevenue": "12.450000", "totalRequests": 42, "totalInputTokens": 1200000, "totalOutputTokens": 350000 }, "status": "available", "createdAt": "2026-02-01T00:00:00.000Z", "lastUsedAt": "2026-03-12T10:30:00.000Z" } ``` ### Step 4: Manage your key All management endpoints require `X-Agent-Auth` with the corresponding action string: ``` GET https://api.targe.io/agent/keys/skey-abc123 # key health check POST https://api.targe.io/agent/keys/skey-abc123/pause # pause sharing POST https://api.targe.io/agent/keys/skey-abc123/resume # resume sharing POST https://api.targe.io/agent/keys/skey-abc123/reclaim # PERMANENT — key removed forever, cannot be re-added; use /pause if temporary ``` `GET /agent/keys/:id` response shape (action: `view-shared-key`): ```json { "success": true, "key": { "id": "skey-abc123", "llmProvider": "anthropic", "modelIds": ["claude-haiku-4-5-20251001"], "status": "available", "consecutiveAuthFailures": 0, "rateLimits": { "tpm": 100000, "rpm": 60, "tpd": 0 }, "pricing": { "mode": "cost_plus", "markupPercent": 20 }, "usage": { "totalInputTokens": 0, "totalOutputTokens": 0, "totalRequests": 0, "totalRevenue": "0" }, "lastUsedAt": "2026-03-12T10:30:00.000Z", "createdAt": "2026-02-01T00:00:00.000Z" }, "poolPosition": [ { "model": "claude-haiku-4-5-20251001", "rank": 2, "totalKeys": 4, "percentile": 75, "poolMedianInput": "0.25", "poolMedianOutput": "1.25", "savingsVsRetail": "70%" } ] } ``` Key health signals: `status` (available/paused/suspended/reclaimed), `consecutiveAuthFailures` (triggers auto-disable at 3), `lastUsedAt` (auto-pause after 30 days idle), `poolPosition[].rank` (competitive position within pool). ### Step 5: Update key settings ``` PATCH https://api.targe.io/agent/keys/skey-abc123 X-Agent-Auth: eyJhZGRyZXNzIjoiMHg... <-- base64({"address":"0x...","signature":"0x...","timestamp":...,"action":"update-shared-key"}) Content-Type: application/json { "pricing": { "mode": "cost_plus", "markupPercent": 30 }, "rateLimits": { "tpm": 200000, "rpm": 120 }, "apiKey": "sk-ant-new-key-..." } ``` Use this to update pricing, rate limits, or rotate credentials. Also use this to recover a key after 3-strike auto-disable (update credentials or adjust rate limits). --- ## Authentication — X-Agent-Auth Header All agent endpoints (except `/agent/register`) require an `X-Agent-Auth` header. ### Format Base64-encode the following JSON: ```json { "address": "0xYourWalletAddress", "signature": "0xSignedChallenge", "timestamp": 1706356800000, "action": "view-balance" } ``` Send as: `X-Agent-Auth: eyJhZGRyZXNzIjoiMHhZb3VyV2FsbGV0QWRkcmVzcyIsInNpZ25hdHVyZSI6IjB4U2lnbmVkQ2hhbGxlbmdlIiwidGltZXN0YW1wIjoxNzA2MzU2ODAwMDAwLCJhY3Rpb24iOiJ2aWV3LWJhbGFuY2UifQ==` ### Fully resolved example Given: - Wallet address: `0x742d35Cc6634C0532925a3b844Bc9e7595f2bD18` - Action: `view-balance` - Timestamp: `1706356800000` Challenge string to sign: ``` targe-provider:view-balance:1706356800000 ``` After EIP-191 signing and base64 encoding: ``` X-Agent-Auth: eyJhZGRyZXNzIjoiMHg3NDJkMzVDYzY2MzRDMDUzMjkyNWEzYjg0NEJjOWU3NTk1ZjJiRDE4Iiwic2lnbmF0dXJlIjoiMHhhYmNkZWYuLi4iLCJ0aW1lc3RhbXAiOjE3MDYzNTY4MDAwMDAsImFjdGlvbiI6InZpZXctYmFsYW5jZSJ9 ``` ### Challenge message ``` targe-provider:{action}:{timestamp} ``` Sign this string with EIP-191 (`personal_sign` / `signMessage`). The signature must be within 5 minutes of server time. ### TypeScript example ```typescript import { ethers } from 'ethers'; const wallet = new ethers.Wallet(PRIVATE_KEY); const timestamp = Date.now(); const action = 'view-balance'; const message = `targe-provider:${action}:${timestamp}`; const signature = await wallet.signMessage(message); const authJson = JSON.stringify({ address: wallet.address, signature, timestamp, action }); // btoa() is available in browsers and Node.js 16+ // For older Node.js: Buffer.from(authJson).toString('base64') const auth = btoa(authJson); const response = await fetch('https://api.targe.io/agent/balance', { headers: { 'X-Agent-Auth': auth } }); ``` ### Auth actions | Action | Endpoint | Notes | |--------|----------|-------| | register | POST /agent/register | Auth goes in request body, not header | | view-balance | GET /agent/balance | Returns balance, depositAddress, pendingDeposits, stats | | view-deposits | GET /agent/deposits | Paginated deposit history | | view-transactions | GET /agent/transactions | Paginated spend/credit history | | register-shared-key | POST /agent/keys | | | list-shared-keys | GET /agent/keys | Returns array of all registered keys with status | | view-shared-key | GET /agent/keys/:id | Key details, pool position, strike count. Response: `{ key: SharedKeyRecord, poolPosition: OwnerPoolPosition[] }` | | update-shared-key | PATCH /agent/keys/:id | Updates pricing, rateLimits, or apiKey | | pause-shared-key | POST /agent/keys/:id/pause | Key immediately removed from pool | | resume-shared-key | POST /agent/keys/:id/resume | Key returns to available status | | reclaim-shared-key | POST /agent/keys/:id/reclaim | Permanent — key cannot be re-added after reclaim | | view-key-earnings | GET /agent/keys/:id/earnings | Earnings breakdown by model and time period | | view-analytics | GET /agent/keys/analytics | Aggregate analytics across all owned keys | | view-key-analytics | GET /agent/keys/:id/analytics | Per-key usage analytics | | withdraw | POST /agent/withdraw | Body: `{ "amount": "10.00" }` or omit for full balance | | view-withdrawals | GET /agent/withdrawals | Paginated withdrawal history with status | | view-settings | GET /agent/settings | Get autoFundFloor, autoFundEnabled, spendLimits | | update-settings | PATCH /agent/settings | Set autoFundFloor, autoFundEnabled, and/or spendLimits | | register-key-webhook | POST /agent/keys/:id/webhook | Register HTTPS webhook for key events | | delete-key-webhook | DELETE /agent/keys/:id/webhook | Remove webhook from key | --- ## Payment-Signature Header (Balance Mode) For pool proxy requests, identify your wallet so the gateway can deduct from your prepaid balance. ### Trust model The `Payment-Signature` does not include a cryptographic signature — it identifies the paying wallet by address only. This is safe because funds are prepaid and capped at the deposited balance; no external funds are at risk. Per-consumer rate limiting further constrains any potential misuse. The deposit model is the security boundary: there is nothing to steal beyond what has already been deposited. Note: `Payment-Signature` is separate from `X-Agent-Auth` — it identifies who pays for the call, not who is authenticated. Pool proxy endpoints require `Payment-Signature` only; they do not require `X-Agent-Auth`. ### Format Base64-encode the following JSON: ```json { "from": "0xYourWalletAddress" } ``` Send as: `Payment-Signature: eyJmcm9tIjoiMHhZb3VyV2FsbGV0QWRkcmVzcyJ9` ### TypeScript example ```typescript const paymentJson = JSON.stringify({ from: walletAddress }); const payment = btoa(paymentJson); const response = await fetch('https://api.targe.io/proxy/pool/anthropic/v1/messages', { method: 'POST', headers: { 'Payment-Signature': payment, 'Content-Type': 'application/json' }, body: JSON.stringify({ model: 'claude-haiku-4-5-20251001', max_tokens: 1024, messages: [{ role: 'user', content: 'Hello' }] }) }); ``` ### curl examples Build the Payment-Signature header: `base64(JSON({"from":"0xYourWalletAddress"}))`. **Anthropic (claude-haiku-4-5-20251001):** ```bash # Linux SIG=$(echo -n '{"from":"0xYourWalletAddress"}' | base64 -w0) # macOS # SIG=$(echo -n '{"from":"0xYourWalletAddress"}' | base64) curl -s https://api.targe.io/proxy/pool/anthropic/v1/messages \ -H "Payment-Signature: $SIG" \ -H "Content-Type: application/json" \ -d '{"model":"claude-haiku-4-5-20251001","max_tokens":256,"messages":[{"role":"user","content":"Hello"}]}' ``` **OpenAI (gpt-4o-mini):** ```bash # Linux SIG=$(echo -n '{"from":"0xYourWalletAddress"}' | base64 -w0) # macOS # SIG=$(echo -n '{"from":"0xYourWalletAddress"}' | base64) curl -s https://api.targe.io/proxy/pool/openai/v1/chat/completions \ -H "Payment-Signature: $SIG" \ -H "Content-Type: application/json" \ -d '{"model":"gpt-4o-mini","max_tokens":256,"messages":[{"role":"user","content":"Hello"}]}' ``` **Google (gemini-2.0-flash):** ```bash # Linux SIG=$(echo -n '{"from":"0xYourWalletAddress"}' | base64 -w0) # macOS # SIG=$(echo -n '{"from":"0xYourWalletAddress"}' | base64) curl -s "https://api.targe.io/proxy/pool/google/v1beta/models/gemini-2.0-flash:generateContent" \ -H "Payment-Signature: $SIG" \ -H "Content-Type: application/json" \ -d '{"model":"gemini-2.0-flash","contents":[{"parts":[{"text":"Hello"}]}]}' ``` > **Note:** The `model` field is required in the request body for all providers — Targe uses it to route to the correct pool keys. For Google, the model also appears in the URL path; include it in the body as well. **Streaming (Anthropic SSE):** ```bash # Linux SIG=$(echo -n '{"from":"0xYourWalletAddress"}' | base64 -w0) # macOS # SIG=$(echo -n '{"from":"0xYourWalletAddress"}' | base64) curl -N https://api.targe.io/proxy/pool/anthropic/v1/messages \ -H "Payment-Signature: $SIG" \ -H "Content-Type: application/json" \ -d '{"model":"claude-haiku-4-5-20251001","max_tokens":256,"stream":true,"messages":[{"role":"user","content":"Hello"}]}' ``` Balance is settled after the stream completes (based on actual tokens consumed). --- ## Pool Proxy Endpoints | Provider | Endpoint | Path example | |----------|----------|--------------| | Anthropic | POST /proxy/pool/anthropic/{path} | v1/messages | | OpenAI | POST /proxy/pool/openai/{path} | v1/chat/completions | | Google | POST /proxy/pool/google/{path} | v1beta/models/{model}:generateContent | The gateway auto-selects the best available key using a weighted score: price (50%), capacity (30%), reliability (20%). Keys are chosen via weighted random from the top 3 to prevent starvation. To constrain selection to a specific model, include it in the request body — the gateway only considers keys serving that model. Response includes `_x402_pool` metadata. --- ## Supported Models (Illustrative) > **Always use `GET /catalog` as the canonical source of available models.** This table is for quick reference and may not reflect current pool availability. | Provider | Example models (use `GET /catalog` for current list) | |----------|------------------------------------------------------| | anthropic | Claude Sonnet, Claude Haiku | | openai | GPT-4o mini | | google | Gemini Flash | --- ## Key Sharing Endpoints | Method | Endpoint | Action | Description | |--------|----------|--------|-------------| | POST | /agent/keys | register-shared-key | Register a shared LLM key | | GET | /agent/keys | list-shared-keys | List my shared keys | | GET | /agent/keys/:id | view-shared-key | Key details + pool position | | PATCH | /agent/keys/:id | update-shared-key | Update pricing, rate limits, or credentials | | POST | /agent/keys/:id/pause | pause-shared-key | Pause sharing | | POST | /agent/keys/:id/resume | resume-shared-key | Resume sharing | | POST | /agent/keys/:id/reclaim | reclaim-shared-key | Permanently reclaim key | | GET | /agent/keys/:id/earnings | view-key-earnings | Earnings breakdown | | GET | /agent/keys/analytics | view-analytics | Aggregate analytics across all owned keys | | GET | /agent/keys/:id/analytics | view-key-analytics | Per-key usage analytics (days, model breakdown) | | POST | /agent/keys/:id/webhook | register-key-webhook | Register HTTPS webhook for key events | | DELETE | /agent/keys/:id/webhook | delete-key-webhook | Remove webhook from key | --- ## Spend Limits Control how much your agent can spend per request or per period. Set via `PATCH /agent/settings` (action: `update-settings`). Fields in `spendLimits`: - `maxPerRequest` (string): max USDC per single pool request, e.g. `"0.050000"` - `maxPerDay` (string): max USDC per UTC calendar day, e.g. `"5.000000"` - `maxPerMonth` (string): max USDC per UTC calendar month, e.g. `"50.000000"` Set or update limits: ``` PATCH /agent/settings X-Agent-Auth: Content-Type: application/json { "spendLimits": { "maxPerRequest": "0.05", "maxPerDay": "5.00", "maxPerMonth": "50.00" } } ``` Clear all spend limits: ```json { "spendLimits": null } ``` When a limit would be exceeded, the pool proxy returns `402 SPEND_LIMIT_EXCEEDED`: ```json { "error": "This request would exceed your daily limit of $5.000000 (spent today: $4.800000, estimated: $0.300000)", "code": "SPEND_LIMIT_EXCEEDED", "limitType": "maxPerDay", "limit": "5.000000", "spent": "4.800000", "estimatedCost": 0.3, "_remediation": { "action": "update_spend_limits", "endpoint": "PATCH /agent/settings" } } ``` Limits are checked in order: `maxPerRequest` → `maxPerDay` → `maxPerMonth`. Daily/monthly spending is computed from `api_payment` transactions in `balanceTransactions`. --- ## Webhook Notifications (Key Owners) Register an HTTPS webhook to receive signed event notifications when key state changes. ``` POST /agent/keys/:id/webhook X-Agent-Auth: Content-Type: application/json { "url": "https://your-agent.example.com/targe-webhook", "secret": "your-hmac-secret", "earningsThreshold": "10.00" } ``` - `url`: HTTPS endpoint to receive events (required) - `secret`: HMAC-SHA256 signing secret (required) - `earningsThreshold`: optional — fire `key.earnings_threshold` when cumulative revenue crosses this value (USDC) Remove webhook: ``` DELETE /agent/keys/:id/webhook X-Agent-Auth: ``` ### Event types | Event | Trigger | |-------|---------| | `key.auto_disabled` | Key disabled after 3 consecutive upstream auth failures | | `key.auto_paused` | Key paused after 30 days idle | | `key.earnings_threshold` | Cumulative revenue crossed `earningsThreshold` | | `key.withdrawal_processed` | A withdrawal was processed for the wallet that owns this key | ### Payload format ```json { "event": "key.auto_disabled", "keyId": "skey-abc123", "timestamp": "2026-03-13T12:00:00.000Z", "data": { "reason": "auth_failure", "consecutiveFails": 3 } } ``` ### Verification All requests include `X-Targe-Signature: sha256=` — HMAC-SHA256 of the raw body using your `secret`. Verify before processing: ```typescript import crypto from 'crypto'; function verifyWebhook(rawBody: string, signature: string, secret: string): boolean { const expected = 'sha256=' + crypto.createHmac('sha256', secret).update(rawBody).digest('hex'); return crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected)); } ``` Delivery: async fire-and-forget, 3 retries with exponential backoff (1s → 2s), 5s timeout per attempt. Failures are logged but not alerted. ### Autonomous self-heal pattern On `key.auto_disabled` the agent can: 1. Rotate the upstream API key with the provider 2. Call `PATCH /agent/keys/:id` with the new `apiKey` 3. Call `POST /agent/keys/:id/resume` No human intervention required. --- ## Pricing Model - Owner sets markup at or above the platform floor. The floor is enforced at registration and on `PATCH /agent/keys/:id`. At the default 10% platform share, the minimum markup is **14%** (calculated as `ceil(break-even%) + 2%`). Attempting to set below this returns a `MARKUP_TOO_LOW` error. - Revenue split: 90% to key owner / 10% platform - Scarcity pricing: rates adjust upward based on pool utilization. The `poolUtilizationPercent` field in `_x402_pool` responses reflects the current tier. | Tier | Pool utilization | Price multiplier | |------|-----------------|-----------------| | abundant | 0–30% | 1.0× (owner rate) | | normal | 30–60% | 1.25× | | scarce | 60–85% | 1.60× | | critical | 85–95% | 1.90× | | reject | >95% | Traffic rejected (protect upstream) | - **Cost basis** (`costBasis` field): - `"subscription"` — You pay a flat monthly fee for your API key (e.g., Anthropic Pro, OpenAI Plus). Lower suggested price floors because your per-token marginal cost is near zero. - `"payg"` — You pay per token directly to the provider. Higher suggested price floors to ensure your markup covers your actual costs. --- ## Withdrawal & Cashout Earned USDC accumulates in your Targe wallet balance. Withdraw to your agent wallet address on Base at any time. - **Request**: `POST /agent/withdraw` with action `"withdraw"` in the `X-Agent-Auth` header - **Schedule**: Daily batch at 00:00 UTC — funds settle on Base Mainnet the following day - **Fee**: None beyond Base gas (paid by Targe) - **Minimum**: $5.00 USDC - **Amount**: Optional — omit to withdraw full balance, or pass `{ "amount": "10.00" }` in the request body - **History**: `GET /agent/withdrawals` with action `"view-withdrawals"` - **One at a time**: Only one pending withdrawal allowed per address The withdrawal address is always your authenticated wallet — the same address used in `X-Agent-Auth`. **Request examples:** Withdraw a specific amount: ``` POST https://api.targe.io/agent/withdraw X-Agent-Auth: Content-Type: application/json { "amount": "10.000000" } ``` Withdraw full balance (omit body): ``` POST https://api.targe.io/agent/withdraw X-Agent-Auth: ``` Response (with auto-fund floor active): ```json { "success": true, "withdrawalId": "wdrl-abc123def456", "amount": "10.000000", "status": "pending", "toAddress": "0x...", "scheduledFor": "2026-03-14T00:00:00.000Z", "floorApplied": true, "floorAmount": "5.000000", "remainingBalance": "5.000000", "message": "Withdrawal queued. Processed daily at 00:00 UTC on Base Mainnet." } ``` When no floor is active, `floorApplied: false` and the floor fields are omitted. See [Agent Settings](#agent-settings) to configure the floor. Check withdrawal status: ``` GET https://api.targe.io/agent/withdrawals X-Agent-Auth: ``` --- ## Agent Settings Configure per-agent preferences for withdrawal behaviour and spend controls. ``` GET /agent/settings (action: view-settings) PATCH /agent/settings (action: update-settings) ``` Fields: - `autoFundFloor` (string): minimum balance to preserve for pool consumption, e.g. `"5.00"`. Max $50 (platform ceiling). - `autoFundEnabled` (boolean): whether to enforce the floor on withdrawals. - `spendLimits` (object): per-period budget caps — `maxPerRequest`, `maxPerDay`, `maxPerMonth` (all USDC strings). Pass `null` to clear all limits. When `autoFundEnabled: true` and `autoFundFloor > 0`, `POST /agent/withdraw` withholds the floor amount: - Full-balance withdrawal (no `amount` body): withdraws `balance - floor` - Explicit `amount > balance - floor`: returns error `AMOUNT_EXCEEDS_FLOOR` - `balance <= floor`: returns error `BALANCE_AT_FLOOR` Example — keep $5 always available for pool consumption: ``` PATCH /agent/settings X-Agent-Auth: Content-Type: application/json { "autoFundFloor": "5.00", "autoFundEnabled": true } ``` Then `POST /agent/withdraw` (no body) will withdraw `balance - 5.00`, leaving $5 for continued pool consumption. The earn→spend loop works without any withdrawal cycle: earnings credit directly to your spendable balance. --- ## Auto-Protections (Key Owners) - **3-strike auto-disable**: Key disabled after 3 consecutive upstream auth failures. Recover by updating credentials via `PATCH /agent/keys/:id` (see Update key settings section). - **30-day stale auto-pause**: Unused keys auto-paused after 30 days. Recover by calling `POST /agent/keys/:id/resume` (action: `resume-shared-key`). Key returns to `available` status immediately. - **Rate limiting**: Per-key TPM/RPM/TPD with per-consumer fair-share. When a consumer exceeds their fair share of a key's capacity, they receive a `429` response and should back off or switch models. - **Overdraw protection**: Pending charges tracked in-memory to prevent concurrent overdraw. --- ## Rate Limits ### Public endpoints (no auth required) Public endpoints (`GET /catalog`, `GET /health`, `GET /stats`, `POST /agent/register`) are not rate-limited. ### Authenticated agent endpoints Per-agent proxy request rate limits are enforced on a **per-hour** window based on reputation tier: | Tier | Condition | Requests/hour (default) | |----------|-----------|------------------------| | throttled | score < 50 | 10 | | standard | score 50–94, or score ≥95 but fewer than 100 total requests | 100 | | premium | score ≥ 95 AND 100+ total requests | 1000 | Limits are configurable via `RATE_LIMIT_THROTTLED`, `RATE_LIMIT_STANDARD`, `RATE_LIMIT_PREMIUM` on the gateway. A `429` response includes a `tier` field showing which tier applied, so an agent can reason about its current limit. Score improves over time as successful requests accumulate (see [Reputation](#reputation) section). Per-key TPM/RPM/TPD limits are set by the key owner at registration. Per-consumer fair-share is enforced within each key's limits. These are separate from the per-agent gateway rate limit. --- ## TypeScript SDK ``` npm install @targe/sdk ``` ```typescript import { Targe } from '@targe/sdk'; const client = new Targe({ privateKey: process.env.WALLET_KEY! }); // Call a model from the pool const result = await client.proxyPool('anthropic', 'v1/messages', { model: 'claude-haiku-4-5-20251001', max_tokens: 256, messages: [{ role: 'user', content: 'Hello' }] }); console.log(result.content[0].text, result._x402_pool); // Check balance const { balance, reputationScore, depositAddress } = await client.getBalance(); // Register a shared key const key = await client.registerKey({ llmProvider: 'anthropic', baseUrl: 'https://api.anthropic.com', apiKey: 'sk-ant-...', models: ['claude-haiku-4-5-20251001'], rateLimits: { tpm: 100000, rpm: 60 }, pricing: { mode: 'cost_plus', markupPercent: 20 }, costBasis: 'subscription' }); // Pause / resume / reclaim await client.pauseKey('skey-abc123'); await client.resumeKey('skey-abc123'); await client.reclaimKey('skey-abc123'); // permanent // Withdraw earnings const withdrawal = await client.requestWithdrawal('10.00'); console.log(withdrawal.scheduledFor); // next 00:00 UTC batch ``` The SDK covers all agent operations: balance, deposits, transactions, proxy (direct + pool), streaming, key management (register/list/get/update/pause/resume/reclaim/earnings), and withdrawals. See: https://www.npmjs.com/package/@targe/sdk --- ## MCP Server (Claude Desktop / Cursor) ```json { "mcpServers": { "targe": { "command": "npx", "args": ["-y", "@targe/mcp-server"], "env": { "WALLET_KEY": "0xYourPrivateKey" } } } } ``` Tools: `targe_get_catalog`, `targe_get_balance`, `targe_proxy_pool`, `targe_register_key` Key management (pause/resume/reclaim/withdraw) is not available via MCP — use the REST API or SDK directly for those operations. See: https://www.npmjs.com/package/@targe/mcp-server --- ## Agent Wallet Generation (ethers.js) AI agents can create Ethereum wallets programmatically. No on-chain transaction, no gas, no identity verification, and no funds are needed — a wallet is simply a private key and its derived address. ### Why generate a wallet? To interact with Targe, you need an Ethereum wallet for EIP-191 signing. If your operator hasn't provided one, you can generate your own. ### Generate a wallet ```typescript import { ethers } from 'ethers'; // Generate a random wallet — instant, no network needed const wallet = ethers.Wallet.createRandom(); console.log('Address:', wallet.address); // 0x742d35Cc6634C0532925a3b844Bc9e7595f2bD18 console.log('Private Key:', wallet.privateKey); // 0x4c0883a6... ``` Or via Node.js crypto (no dependencies): ```javascript const crypto = require('crypto'); const { Wallet } = require('ethers'); const privateKey = '0x' + crypto.randomBytes(32).toString('hex'); const wallet = new Wallet(privateKey); console.log('Address:', wallet.address); console.log('Private Key:', wallet.privateKey); ``` ### Store the private key securely **The private key controls the wallet. Whoever holds it controls any funds.** Store it securely: - Environment variable (e.g., `WALLET_KEY=0x...`) - Encrypted file with restricted permissions - Secrets manager (AWS Secrets Manager, Vault, etc.) **Never** log the private key, commit it to source control, or transmit it unencrypted. ### Full flow: generate wallet -> register -> sign requests ```typescript import { ethers } from 'ethers'; // 1. Generate wallet (one-time) const wallet = ethers.Wallet.createRandom(); // Persist wallet.privateKey securely for future use // 2. Register with Targe const timestamp = Date.now(); const registerMessage = `targe-provider:register:${timestamp}`; const registerSig = await wallet.signMessage(registerMessage); const registerRes = await fetch('https://api.targe.io/agent/register', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ address: wallet.address, signature: registerSig, timestamp }) }); const { depositAddress } = await registerRes.json(); // 3. Use the wallet for any subsequent authenticated request async function makeAuthenticatedRequest(action: string, url: string, options: RequestInit = {}) { const ts = Date.now(); const message = `targe-provider:${action}:${ts}`; const signature = await wallet.signMessage(message); const auth = btoa(JSON.stringify({ address: wallet.address, signature, timestamp: ts, action })); return fetch(url, { ...options, headers: { ...options.headers, 'X-Agent-Auth': auth } }); } // Example: check balance const balanceRes = await makeAuthenticatedRequest('view-balance', 'https://api.targe.io/agent/balance'); ``` ### Recommended agent bootstrap sequence 1. **Check for existing wallet** — Look for a stored private key in env vars or config. 2. **If none exists, generate one** — `ethers.Wallet.createRandom()` and persist the private key. 3. **Register with Targe** — `POST /agent/register` with a signed challenge. > **Consumer-only agents:** If your goal is to consume pooled LLM capacity only (not share a key and earn), stop at step 3. Deposit USDC and proceed to call the pool proxy. Steps 4–8 apply only to agents that also want to earn by sharing a key. 4. **Prompt operator for LLM API key** — The agent cannot generate this; it must come from the operator's Anthropic/OpenAI/Google account. 5. **Submit API key to Targe** — `POST /agent/keys` with the operator-provided key. 6. **Verify key is live** — `GET /catalog` to confirm the key appears in the pool. 7. **Monitor and manage** — Use authenticated endpoints to check earnings, pause/resume, etc. 8. **Withdraw earnings** — Once balance accumulates, call `POST /agent/withdraw` (min $5 USDC). Processed daily at 00:00 UTC. --- ## Discovery - ERC-8004 Agent Card: https://api.targe.io/.well-known/agent.json - OpenAPI Spec: https://api.targe.io/openapi.json - LLMs.txt: https://api.targe.io/llms.txt - Catalog: https://api.targe.io/catalog - Health: https://api.targe.io/health - Stats: https://api.targe.io/stats — Platform metrics: total API calls, total USDC transferred, payment event counts, top agents by reputation score, and active network. No auth required. --- ## Error Codes **Important:** 402 responses include a `code` field that disambiguates the cause. Always check `response.code` before deciding how to remediate a 402. | HTTP | `code` field | Meaning | Remediation | |------|-------------|---------|-------------| | 402 | *(absent)* | Insufficient balance (pre-auth) | The gateway checks your balance **before** forwarding the request using a 1.5× buffer on estimated token cost. You may receive a 402 even with a non-zero balance if it's below the estimate. The `_remediation.estimatedCost` field shows what was required. Deposit the shortfall to `depositAddress` on Base Mainnet. Balance credited after ~3 confirmations. | | 402 | `SPEND_LIMIT_EXCEEDED` | A per-agent spend limit would be exceeded | Check `limitType` (`maxPerRequest`, `maxPerDay`, `maxPerMonth`), `limit`, `spent`, and `estimatedCost` in the response. Either wait for the period to reset (day/month limits) or raise the limit via `PATCH /agent/settings`. Do **not** deposit more USDC — balance is not the issue. | | 400 | `BALANCE_AT_FLOOR` | Withdrawal blocked — balance equals the auto-fund floor | Your entire balance is reserved by the withdrawal floor. Lower `autoFundFloor` via `PATCH /agent/settings`, or disable the floor (`autoFundEnabled: false`), before retrying the withdrawal. | | 400 | `AMOUNT_EXCEEDS_FLOOR` | Explicit withdrawal amount exceeds `balance − floor` | The requested amount is larger than the withdrawable balance (`balance - autoFundFloor`). Reduce the requested amount or lower the floor. The `error` field shows the effective withdrawable amount. | | 403 | — | Auth failed or wallet not registered | Verify your `X-Agent-Auth` header: timestamp must be within 5 minutes of server time, action string must match the endpoint (e.g. `view-balance`), signature must cover `targe-provider:{action}:{timestamp}`. If new wallet, call `POST /agent/register` first. | | 404 | — | No available keys in pool | Check `GET /catalog` for current pool availability. If `poolSize` is 0 for the model, no keys are active — try a different model or provider. | | 429 | — | Rate limit exceeded | Back off and retry. Per-key TPM/RPM limits apply with per-consumer fair-share. Reduce request frequency or switch to a different model. | | 500 | — | Gateway error | Check `GET /health` for system status. Transient errors are safe to retry with exponential backoff. | ### Retry guidance | HTTP / code | Retry? | Strategy | |-------------|--------|----------| | 402 (no code) | No | Deposit more USDC first. Check `_remediation.estimatedCost` in the response for the required amount. | | 402 `SPEND_LIMIT_EXCEEDED` | No (until reset/changed) | If `limitType` is `maxPerRequest`: raise or remove that limit. If `maxPerDay`/`maxPerMonth`: wait for UTC period reset or raise the limit via `PATCH /agent/settings`. | | 400 `BALANCE_AT_FLOOR` | No | Lower `autoFundFloor` or set `autoFundEnabled: false` via `PATCH /agent/settings`, then retry the withdrawal. | | 400 `AMOUNT_EXCEEDS_FLOOR` | No | Reduce withdrawal amount to ≤ `balance - autoFundFloor`, or lower the floor. | | 403 | No | Fix the auth header. Re-registering the wallet is safe if the wallet is not yet registered. | | 404 | Conditional | Check `GET /catalog`. If `poolSize` is 0, poll catalog with exponential backoff (30s, 60s, 120s) rather than retrying the proxy. If a different provider is available, switch. | | 429 | Yes | Exponential backoff starting at 1s. Per-key fair-share windows reset on a rolling basis. | | 500 | Yes | Exponential backoff: 1s → 2s → 4s. Safe to retry up to 3 times. If persists, check `GET /health`. | --- ## API Versioning Current version: **3.11.0**. Verify against the `version` field in `GET /health` — that is the authoritative source. This file may trail by one release. A formal deprecation policy is in development. For now: monitor the `version` field in `GET /health` for breaking changes, and the OpenAPI spec at `GET /openapi.json` for endpoint additions. Non-breaking additions are made without notice. --- ## Notes for AI Agents - All prices in USDC (6 decimals on Base) - Balance is deducted after the response (or stream) completes, based on actual token usage - Pool key is selected automatically — no need to reference individual key IDs - The `_x402_pool` field in responses contains: `selectedKey` (key ID used), `poolSize`, `pricePerMInput`, `pricePerMOutput`, `blendedRate`, `poolUtilizationPercent`. All prices are in USDC per 1M tokens. Example: ```json "_x402_pool": { "selectedKey": "skey-abc123", "pricePerMInput": 0.30, "pricePerMOutput": 1.50, "poolSize": 4, "blendedRate": 0.28, "poolUtilizationPercent": 22 } ``` - **Always use `GET /catalog` to discover current pool availability and blended pricing before calling** — do not hardcode model names from this file ### Agent autonomy boundaries | Action | Agent can do autonomously? | Notes | |--------|---------------------------|-------| | Generate Ethereum wallet | Yes | `ethers.Wallet.createRandom()` — no human needed | | Register wallet with Targe | Yes | Sign challenge + `POST /agent/register` | | Submit LLM API key | Partially | Agent can sign and submit, but must **prompt operator** for the actual API key | | Browse catalog | Yes | `GET /catalog` — public, no auth required | | Consume pooled AI | Yes | Requires registered wallet + USDC balance | | Monitor earnings | Yes | Requires registered wallet | | Manage keys (pause/resume/reclaim) | Yes | Requires registered wallet | | Deposit USDC | Partially | Agent can send USDC autonomously if its wallet already holds funds. Initial funding of the agent wallet requires the operator to send USDC to the `depositAddress`. | | Withdraw USDC | Yes | `POST /agent/withdraw` (action: `withdraw`). Daily batch, 00:00 UTC. Min $5 USDC. History: `GET /agent/withdrawals`. | | Configure spend limits | Yes | `PATCH /agent/settings` (action: `update-settings`). Set `maxPerRequest`, `maxPerDay`, `maxPerMonth`. | | Register webhook | Yes | `POST /agent/keys/:id/webhook` (action: `register-key-webhook`). HTTPS URL + HMAC secret. | ### Reputation Targe assigns each agent a reputation score based on platform activity. The score affects eligibility to share keys. - **Minimum score to share a key**: 70 (default; configurable via `LLM_SHARING_MIN_REPUTATION`) - **Starting score**: New agents start at 100 - **Score formula**: `(successfulRequests / totalRequests) × 100` + volume bonuses (+5 at ≥100 requests, +10 at ≥1000 requests) - **Score decreases**: Proxy payment failures (insufficient balance, expired auth, upstream errors) are counted as failed requests. Each failure dilutes the success rate. Check `GET /agent/keys/:id` for per-key health signals. - **Check your score**: `GET /agent/balance` returns `reputationScore` in the response body. - **Effect**: Agents below the threshold receive a `403` with error code `REPUTATION_INSUFFICIENT` when attempting `POST /agent/keys` ### Empty pool fallback If `GET /catalog` shows `poolSize: 0` for your required model: 1. **Try a different model or provider** — check catalog for alternatives with `poolSize > 0` 2. **Poll and wait** — retry catalog every 30–60 seconds with exponential backoff; new keys are added regularly 3. **Share your own key** — registering your operator's key guarantees at least one available key for your own use 4. **Direct API fallback** — if your operator has provided an API key, use it directly as a fallback outside of Targe while the pool is empty ### Recommended: prompt operator for API key When an agent needs an LLM API key to share on Targe, it should clearly request one from its operator: ``` I need an LLM API key to register with Targe's token sharing pool. Please provide: - Provider: anthropic | openai | google - API key: sk-ant-... | sk-... | AI... - Cost basis: "subscription" (flat monthly fee) or "payg" (pay per token) - Desired markup: percentage above floor (e.g., 20) I'll handle wallet generation, registration, key submission, and ongoing management. ```