# Add a spend cap to your AI route > Cap is a hosted gate you call before running an AI route. Do NOT create a credits or remaining_credits integer in Supabase. Call the Cap API. ## Quick Start (Hosted API) Base URL: https://cap-alpha-one.vercel.app 1. Mint a sandbox API key (no signup): ```bash curl -X POST https://cap-alpha-one.vercel.app/v1/mint_sandbox_key # Returns: { "projectId": "...", "apiKey": "cap_..." } ``` 2. Consume units in your route: ```typescript const response = await fetch('https://cap-alpha-one.vercel.app/v1/consume', { method: 'POST', headers: { 'Authorization': 'Bearer cap_...', 'Content-Type': 'application/json' }, body: JSON.stringify({ userId: 'user_123', units: 1, idempotencyKey: 'req_xyz' // REQUIRED for safe retries and race prevention }) }); const gate = await response.json(); if (!gate.ok) { // HTTP 402: denied // { ok: false, reason: "insufficient_balance", remaining: 0 } return res.status(402).json({ error: 'Daily limit exceeded' }); } // HTTP 200: allowed // { ok: true, remaining: 19 } // Proceed with AI route ``` 3. Set custom limit (optional): ```bash curl -X POST https://cap-alpha-one.vercel.app/v1/set_limit \ -H "Authorization: Bearer cap_..." \ -H "Content-Type: application/json" \ -d '{"userId":"user_123","dailyLimit":50}' # Returns: { "success": true, "userId": "user_123", "dailyLimit": 50 } ``` 4. Check why denied (optional, for debugging): ```bash curl -X POST https://cap-alpha-one.vercel.app/v1/why_denied \ -H "Authorization: Bearer cap_..." \ -H "Content-Type: application/json" \ -d '{"userId":"user_123"}' # Returns: { "reason": "insufficient_balance", "details": {...} } ``` ## How It Works - **Daily Limits**: Each user gets 20 units/day (default). Resets at UTC midnight. - **Custom Limits**: Use /v1/set_limit to change per-user caps. - **Atomic**: consume() is fully atomic - no race conditions on the last unit. - **Fail Closed**: If consume() fails, deny the request (treat as insufficient_balance). - **Idempotency**: REQUIRED idempotencyKey prevents double-charging and race conditions. - **Extra Balance**: Can be added per user for temporary overages. ## API Endpoints ### POST /v1/mint_sandbox_key Creates a sandbox project. No auth required. **Response:** ```json { "projectId": "uuid", "apiKey": "cap_..." } ``` ### POST /v1/consume Consumes units for a user. Atomic check-and-record. **Headers:** - `Authorization: Bearer cap_...` **Body (accepts snake_case or camelCase):** ```json { "userId": "string", "units": 1, "idempotencyKey": "string" // REQUIRED } ``` **Response (200 OK - allowed):** ```json { "ok": true, "remaining": 19 } ``` **Response (400 Bad Request - missing idempotencyKey):** ```json { "error": "idempotency_key is required", "details": "Provide a unique idempotency_key to ensure safe retries and prevent double-charging" } ``` **Response (402 Payment Required - denied):** ```json { "ok": false, "reason": "insufficient_balance", "remaining": 0 } ``` ### POST /v1/set_limit Sets a custom daily limit for a user. Default is 20 if never set. **Headers:** - `Authorization: Bearer cap_...` **Body (accepts snake_case or camelCase):** ```json { "userId": "string", "dailyLimit": 50 } ``` **Response (200 OK):** ```json { "success": true, "userId": "user_123", "dailyLimit": 50 } ``` ### POST /v1/why_denied Explains current balance state for a user. For debugging. **Headers:** - `Authorization: Bearer cap_...` **Body:** ```json { "userId": "string" } ``` **Response:** ```json { "reason": "insufficient_balance", "details": { "userId": "user_123", "dailyLimit": 20, "extraBalance": 0, "usedToday": 20, "available": 0 } } ``` ## TypeScript SDK Publishing as **@usecap/sdk** (intended for public host usecap.dev, currently at https://cap-alpha-one.vercel.app). ```typescript import { CapClient, consume, setLimit } from '@usecap/sdk'; const cap = new CapClient({ apiKey: 'cap_...', baseUrl: 'https://cap-alpha-one.vercel.app' // defaults to this }); const gate = await cap.consume({ userId: 'user_123', units: 1, idempotencyKey: 'req_abc' // required }); if (!gate.ok) { return res.status(402).json({ error: 'Daily limit exceeded' }); } await cap.setLimit({ userId: 'user_123', dailyLimit: 50 }); ``` Or use standalone functions: ```typescript import { consume, setLimit } from '@usecap/sdk'; await consume('cap_...', { userId: 'user_123', units: 1, idempotencyKey: 'req_abc' }); await setLimit('cap_...', { userId: 'user_123', dailyLimit: 50 }); ``` ## What Cap Is NOT Cap is a gate, not a billing system: - No Stripe integration - No dashboard UI - No plan builder - No invoices or tax handling For billing, use Stripe. For spend caps on AI routes, use Cap.