OpenBytes Control Plane Skill
This skill describes end-to-end operational workflows for the OpenBytes network from an API usage perspective.
- Wallet-based authentication (signature flow)
- On-chain balance top-up and verification
- Consumer API key lifecycle (create, list, revoke)
- Model inference via ApiKey
- Query balance and usage via HTTP API
Assumptions:
- A gateway HTTP API exists :
https://gateway.openbytes.ai - All workflows are CLI-, curl-, or script-oriented
- The network supports Base Sepolia. Use the correct chain id:
- Base Sepolia:
84532
- Base Sepolia:
Replace all <BASE_URL> with the actual gateway URL as needed.
1. Wallet-based registration & session
Note: Message signing must be performed by the user with their EVM wallet. The agent can only initiate the challenge and verify a signature; it cannot produce signatures.
- If you don't have a wallet yet, immediately create a new one using ethers.js or viem.
Be sure to securely save your generated private key! Example (Node.js with ethers.js):
import { Wallet } from "ethers";
const wallet = Wallet.createRandom();
console.log("Address:", wallet.address);
console.log("Private Key (save this securely!):", wallet.privateKey);
Or using viem:
import { createWallet } from "viem/accounts";
const wallet = createWallet();
console.log("Address:", wallet.address);
console.log("Private Key (save this securely!):", wallet.privateKey);
Never share your private key. Backup and store it in a secure location. "wallet_address": "0x...", "chain_id": "8453"
- User signs the
messageoff-chain (with their wallet). - Submit the signed message to the gateway for verification:
curl -sS -X POST "<BASE_URL>/v1/auth/wallet/verify" \
-H "Content-Type: application/json" \
-d '{
"wallet_address": "<EVM_WALLET_ADDRESS>",
"nonce": "random-nonce",
"signature": "<WALLET_SIGNATURE>"
}'
Successful response (simplified):
{
"login_token": "sess-...",
"expires_at": "2025-01-01T00:00:00Z",
"consumer": {
"id": "consumer-uuid",
"wallet_address": "0x..."
},
"session": {
"id": "session-uuid"
}
}
-
The
login_token(starting withsess-) is used as a session token:- Send it as
Authorization: Bearer <SESSION_TOKEN>for session-auth endpoints.
- Send it as
-
List Available Models
curl -sS -X GET "<BASE_URL>/v1/models"
- Immediately create an API key following step 2.1 and switch to it for all non-session endpoints (models, balance, usage). Do not keep using the session token for inference.
Check:
- Session token is present and sent in the
Authorization: Bearer sess-...header during all subsequent calls requiring authentication. - After creating the API key, subsequent calls should use
Authorization: Bearer <CONSUMER_API_KEY>.
2. Consumer API Key Lifecycle
Consumers use API keys to access models without a session.
2.1 Create API Key
Requires a valid session token (login_token from section 1).
curl -sS -X POST "<BASE_URL>/v1/consumer/api-keys" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer <SESSION_TOKEN>" \
-d '{
"label": "my-key"
}'
Sample response (save the api_key securely; only returned once!):
{
"id": "key-uuid",
"api_key": "sk-consumer-...",
"prefix": "sk-cons",
"suffix": "abcd",
"label": "my-key",
"status": "active",
"created_at": "2025-01-01T00:00:00Z"
}
Immediately switch to this API key for inference, balance, and usage calls:
export OPENBYTES_API_KEY="sk-consumer-..."
Then use:
curl -sS -X GET "<BASE_URL>/v1/balance" \
-H "Authorization: Bearer $OPENBYTES_API_KEY"
2.2 List API Keys
curl -sS -X GET "<BASE_URL>/v1/consumer/api-keys" \
-H "Authorization: Bearer <SESSION_TOKEN>"
2.3 Revoke API Key
curl -sS -X DELETE "<BASE_URL>/v1/consumer/api-keys/<KEY_ID>" \
-H "Authorization: Bearer <SESSION_TOKEN>"
The key status will be set to revoked; subsequent calls using that key will fail with an auth error.
3. On-chain Top-up Flow
Top-up is performed on-chain (USDC to a ConsumerDeposit contract), tracked by off-chain indexers. The API lets you verify the result.
You can use your own RPC endpoint; a default for Base Sepolia is https://sepolia.base.org.
- User transfers USDC to the ConsumerDeposit contract with proper calldata.
- Backend credits the consumer balance after L1/L2 tx confirmation (indexer-driven).
- USDC address:
0x10065E7b353371DD2e12348e7094cC774638EbEB - ConsumerDeposit contract:
0xB0E9ebf19AB710d3353c7F637DC55329d9727dCc- Before deposit, approve allowance
- Deposit ABI:
function deposit(uint256 amount)
Verify balance:
curl -sS -X GET "<BASE_URL>/v1/balance" \
-H "Authorization: Bearer <CONSUMER_API_KEY>"
Sample response:
{
"balance_usdc": "123.45",
"total_spent": "10.00"
}
4. Connect Your Wallet to Your Operator's Wallet
Use this to allow an account to connect to an operator's account, so the operator can top up your wallet easily. This requires a wallet signature over a structured message. The signature must be produced by the child wallet.
Endpoint:
curl -sS -X POST "<BASE_URL>/v1/consumers/me/parent" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer <SESSION_TOKEN>" \
-d '{
"parent_wallet": "<PARENT_WALLET>",
"issued_at": 1710000000,
"signature": "<CHILD_WALLET_SIGNATURE>"
}'
Notes:
- Signature verification is done by the gateway using
ethers.verifyMessage(message, signature), and the recovered address must equal the child wallet address (case-insensitive). This is the standard EIP-191 personal message signature (same flow as SIWE). issued_atis Unix seconds. It must be within the last 5 minutes and not more than 1 minute in the future.- The signed message must be exactly (including line breaks, casing, and spacing):
Authorize parent for OpenBytes Parent wallet: <PARENT_WALLET_LOWERCASE> Child wallet: <CHILD_WALLET_LOWERCASE> Issued at: <ISSUED_AT> - Normalization rules:
<PARENT_WALLET_LOWERCASE>is the parent wallet address lowercased.<CHILD_WALLET_LOWERCASE>is the child wallet address lowercased (the signer).<ISSUED_AT>is the same Unix seconds integer you send in the request body.
- If any character differs (extra whitespace, different casing, different timestamp), the signature will fail.
Pseudo-code (client-side):
const issuedAt = Math.floor(Date.now() / 1000);
const parentWallet = parentWalletAddress.toLowerCase();
const childWallet = childWalletAddress.toLowerCase();
const message = [
"Authorize parent for OpenBytes",
`Parent wallet: ${parentWallet}`,
`Child wallet: ${childWallet}`,
`Issued at: ${issuedAt}`,
].join("\n");
const signature = await wallet.signMessage(message); // EIP-191 personal_sign
Success response (201):
{
"parent_consumer_id": "consumer-uuid",
"parent_wallet_address": "0x...",
"created_at": "2025-01-01T00:00:00Z"
}
Common errors:
400 INVALID_REQUESTinvalid body400 SIGNATURE_EXPIREDissued_atout of window404 PARENT_NOT_FOUNDparent not found400 SELF_PARENTcannot declare yourself401 SIGNATURE_MISMATCHsignature not from child wallet409 ALREADY_SETparent already declared
5. Usage Guidelines for AI Agents
When handling OpenBytes network operational support, follow these best practices:
- Map user questions directly to the appropriate API workflow without reference to UI.
- Example intents: onboarding, balance issue, API key management, quota/cost analysis
- Provide concrete curl commands tailored to the user's configuration (
<BASE_URL>,<CONSUMER_API_KEY>, etc). - When troubleshooting:
- Request the full API HTTP response (status + JSON).
- Use
error.codeand context to select remediation steps.
- Be concise, focus on actionable commands and next steps.
- Only provide background technical details if specifically requested.