Skip to content

Frontend Integration

The gateway is a credit-based LLM inference API. Users fund a USDC balance, authenticate with their wallet (SIWE), get an API key, and call the inference endpoint.

User Flow

Connect wallet → Top up USDC → Sign in (SIWE) → Create API key → Use API key for completions

Top-up and sign-in are independent -- both require a connected wallet but can happen in either order.

Base URL

EnvironmentOrigin
Local devhttp://localhost:8080
Staginghttps://agent-router.gaib.cloud
PurposeLibrary
Wallet connectionwagmi, RainbowKit, ConnectKit
SIWE message constructionsiwe
Viem wallet clientviem
x402 payment@x402/fetch or @x402/client
OpenAI-compatible clientopenai (point baseURL at gateway)

Top Up USDC Balance

Top-up uses the x402 protocol. The client pays USDC on Base before the balance is credited.

Step 1 -- Discover payment requirements

http
POST /v1/topup
Content-Type: application/json

{ "amount": 5 }

Response (no payment header present):

json
// HTTP 402
{
  "accepts": [{
    "scheme": "exact",
    "network": "eip155:8453",
    "maxAmountRequired": "5000000",
    "asset": "0x...",
    "payTo": "0x...",
    "extra": { "name": "USDC", "decimals": 6 }
  }]
}

Step 2 -- Sign ERC-3009 and send payment

ts
import { withPaymentInterceptor } from '@x402/fetch'

const fetchWithPayment = withPaymentInterceptor(fetch, walletClient)

const res = await fetchWithPayment(`${BASE_URL}/v1/topup`, {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ amount: 5 }),
})
const { balance_usdc, credited_usdc } = await res.json()
// balance_usdc: total balance in micro-USDC (6 decimals)
// $1 = 1_000_000 micro-USDC

SIWE Authentication

SIWE proves wallet ownership for API key management. No server-side session -- every call needs a fresh signed message (valid 5 minutes).

ts
import { SiweMessage } from 'siwe'
import { createWalletClient, custom } from 'viem'
import { mainnet } from 'viem/chains'

const walletClient = createWalletClient({
  chain: mainnet,
  transport: custom(window.ethereum),
})
const [address] = await walletClient.getAddresses()

const siweMsg = new SiweMessage({
  domain: window.location.host,
  address,
  uri: window.location.origin,
  version: '1',
  chainId: 1,
  nonce: crypto.randomUUID().replace(/-/g, '').slice(0, 16),
  issuedAt: new Date().toISOString(),
  statement: 'Sign in to the AI Gateway',
})

const message = siweMsg.prepareMessage()
const signature = await walletClient.signMessage({
  account: address,
  message,
})

TIP

Generate a fresh issuedAt timestamp immediately before each API call. The server rejects SIWE messages older than 5 minutes.

API Key Management

API keys are sk-<64 hex chars> tokens. Returned once on creation -- store immediately.

Create a key

http
POST /v1/auth/keys
Content-Type: application/json

{
  "message": "<siwe message string>",
  "signature": "<hex signature>",
  "label": "my-app"
}

Response 201:

json
{
  "id": 1,
  "key": "sk-a3f9...",
  "label": "my-app",
  "created_at": "2026-04-07T..."
}

List keys

http
GET /v1/auth/keys?message=<siwe>&signature=<sig>

Revoke a key

http
DELETE /v1/auth/keys/:key_id
Content-Type: application/json

{
  "message": "<siwe message string>",
  "signature": "<hex signature>"
}

Chat Completions

OpenAI-compatible. Pass the API key as a Bearer token.

http
POST /v1/chat/completions
Authorization: Bearer sk-a3f9...
Content-Type: application/json

{
  "model": "gemini/gemini-2.5-flash",
  "messages": [
    { "role": "system", "content": "You are a helpful assistant." },
    { "role": "user", "content": "Hello!" }
  ],
  "max_tokens": 512,
  "stream": false
}

Using the OpenAI SDK

ts
import OpenAI from 'openai'

const client = new OpenAI({
  baseURL: `${BASE_URL}/v1`,
  apiKey: 'sk-a3f9...',
  dangerouslyAllowBrowser: true,
})

const response = await client.chat.completions.create({
  model: 'gemini/gemini-2.5-flash',
  messages: [{ role: 'user', content: 'Hello!' }],
})

Streaming (SSE)

Set "stream": true for server-sent events:

ts
const res = await fetch(`${BASE_URL}/v1/chat/completions`, {
  method: 'POST',
  headers: {
    'Authorization': `Bearer ${apiKey}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({ model, messages, max_tokens: 512, stream: true }),
})

const reader = res.body!.getReader()
const decoder = new TextDecoder()

while (true) {
  const { done, value } = await reader.read()
  if (done) break
  const chunk = decoder.decode(value)
  for (const line of chunk.split('\n')) {
    if (!line.startsWith('data: ')) continue
    const data = line.slice(6)
    if (data === '[DONE]') break
    const delta = JSON.parse(data).choices[0].delta.content ?? ''
    // append delta to your UI
  }
}

Units & Conversions

ValueUnitDisplay
balance_usdcmicro-USDC (6 decimals)balance_usdc / 1_000_000 -> USD
amount in topupUSD (float)e.g. 5 = $5
promptPricePer1MTokensUSD per 1M tokens--
total_charged_usdcmicro-USDC/ 1_000_000 -> USD

Released under the MIT License.