1. Authorize Agent
Creates or updates an authorization for an agent to charge the user's account.
Endpoint:
POST /authorize
Request Body:
json{ "agent": "0x...", // Agent's wallet address "maxPerCharge": "1000000", // Max USDC per charge (6 decimals) "totalLimit": "50000000", // Total authorization limit in USDC "rateLimit": 100, // Max charges per hour "expiry": 1735689600, // Unix timestamp for expiration "disputeWindow": 7200, // Length of dispute window in seconds "nonce": "abc123", // Unique identifier "signature": "0x..." // EIP-712 signature of the authorization }
EIP-712 Message Type:
solidityAuthorization( address agent, uint256 maxPerCharge, uint256 totalLimit, uint256 rateLimit, uint256 disputeWindow, uint256 expiry, string nonce )
Response:
json{ "authId": "0x...", // Authorization ID (keccak256 hash) "user": "0x...", // User's wallet address (recovered from signature) "agent": "0x...", // Agent's wallet address "status": "active", "created": 1735603200 }
Errors:
400
- Invalid signature or parameters
409
- Authorization already exists with same nonce
2. Charge User
Submits a charge against an existing authorization, or test whether a charge will succeed (dry run mode).
Endpoint:
POST /charge
Request Body:
json{ "user": "0x...", // User's wallet address "amount": "100000", // Amount in USDC (6 decimals) "authId": "0x...", // Authorization ID "metadata": { // Optional attribution data "taskId": "task-123", "service": "gpt-4", "units": 1000 }, "nonce": "xyz789", // Unique charge identifier "signature": "0x...", // Agent's EIP-712 signature "dryRun": false // Optional, if set to true, the charge won't be submitted. `signature` is ignored and always considered to be correct in dry run mode. }
EIP-712 Message Type:
solidityCharge( address user, uint256 amount, bytes32 authId, string metadata, string nonce )
Response:
json{ "chargeId": "0x...", // Charge ID (keccak256 hash) "status": "pending", // pending | settled | disputed "settleBy": 1735603230, // Expected settlement time "disputeWindow": 1735610400 // Dispute deadline (2 hours) }
Errors:
400
- Invalid signature or parameters
401
- Agent not authorized
402
- Insufficient balance
409
- Duplicate charge (nonce already used)
429
- Rate limit exceeded
3. Dispute Charge
Allows users to dispute a charge within the dispute window.
Endpoint:
POST /dispute
Request Body:
json{ "chargeId": "0x...", // Charge ID to dispute (computed from transaction log) "reason": "Service not provided", // Dispute reason "signature": "0x..." // User's EIP-712 signature }
EIP-712 Message Type:
solidityDispute( bytes32 chargeId, string reason )
Response:
json{ "disputeId": "0x...", // Dispute transaction ID "chargeId": "0x...", "status": "submitted", // submitted | resolved | rejected "refundAmount": "100000", // Amount to be refunded "processBy": 1735606800 // Expected resolution time }
Errors:
400
- Invalid signature or charge ID
401
- Not authorized (not the charged user)
403
- Dispute window expired
409
- Charge already disputed
4. Top Up Account
Returns transaction details for depositing USDC to the user's account.
Endpoint:
GET /topup
Query Parameters:
plain text?user=0x... // User's wallet address &amount=1000000 // Amount in USDC (optional)
Response:
json{ "to": "0x...", // Target contract address (BillingLedger) "data": "0x...", // Encoded transaction data "value": "0" // ETH value (always 0 for USDC) }
Errors:
400
- Invalid wallet address
5. Withdraw accumulated fees to agent wallet
Returns transaction details for withdrawing USDC to the agent's account.
Endpoint:
GET /w/
Query Parameters:
plain text?agent=0x... // User's wallet address &amount=1000000 // Amount in USDC (optional)
Response:
json{ "to": "0x...", // Target contract address (BillingLedger) "data": "0x...", // Encoded transaction data "value": "0" // ETH value (always 0) }
Errors:
400
- Invalid wallet address
Common Response Headers
All responses include:
plain textX-Request-Id: uuid-v4 X-Rate-Limit-Remaining: 100 X-Rate-Limit-Reset: 1735603200
Error Response Format
json{ "error": { "code": "insufficient_balance", "message": "User balance insufficient for charge", "details": { "available": "50000", "required": "100000" } } }
Signature Verification
All signatures must be verifiable by one of the following way:
- EOA wallets: Standard ECDSA recovery
- Smart wallets: EIP-1271
isValidSignature(bytes32 hash, bytes signature)
The API backend verifies signatures off-chain for performance, but all critical operations are verified on-chain during settlement.
Rate Limits
- Default: 100 requests per minute per wallet
- Charge endpoint: Subject to authorization-specific rate limits
- Exceeded limits return
429
status with retry-after header
Example: Complete Flow
javascript// 1. User authorizes agent const authorization = { agent: "0xAgent...", maxPerCharge: "1000000", totalLimit: "50000000", rateLimit: 100, expiry: Math.floor(Date.now() / 1000) + 86400, nonce: crypto.randomUUID() }; const signature = await wallet.signTypedData({ domain: GRINDERY_DOMAIN, types: { Authorization: [...] }, value: authorization }); const authResponse = await fetch('/authorize', { method: 'POST', body: JSON.stringify({ ...authorization, signature }) }); // 2. Agent charges user const charge = { user: "0xUser...", amount: "100000", authId: authResponse.authId, metadata: { taskId: "task-123" }, nonce: crypto.randomUUID() }; const agentSig = await agentWallet.signTypedData({ domain: GRINDERY_DOMAIN, types: { Charge: [...] }, value: charge }); await fetch('/charge', { method: 'POST', body: JSON.stringify({ ...charge, signature: agentSig }) });