# BlueNexus - Complete Integration Reference

> This file is designed to be loaded as context by AI coding assistants (Claude Code, Cursor, Windsurf, etc.) when building applications that integrate with BlueNexus. It contains every endpoint, authentication pattern, request/response shape, and framework example you need.

## What is BlueNexus?

BlueNexus is a Universal MCP server platform. It gives AI applications access to users' data across 100+ services (Google Workspace, Slack, Notion, GitHub, Jira, HubSpot, Salesforce, Stripe, and more) through a single MCP endpoint. BlueNexus handles OAuth, token management, credential encryption, and context optimization.

**Interactive API documentation (Swagger UI) is available at: https://api.bluenexus.ai/api/docs** - use this as the authoritative reference for request/response schemas, parameter types, and endpoint details.

## Important URLs

| Resource | URL |
|----------|-----|
| MCP Endpoint | `https://api.bluenexus.ai/mcp` |
| REST API Base | `https://api.bluenexus.ai/api/v1` |
| Swagger UI (API docs) | https://api.bluenexus.ai/api/docs |
| Dashboard | https://app.bluenexus.ai |
| Developer Portal (API keys) | https://app.bluenexus.ai/developer/clients |
| Connector Catalog | https://www.bluenexus.ai/connectors |
| Documentation | https://docs.bluenexus.ai |

---

## MCP Endpoint

```
POST https://api.bluenexus.ai/mcp
```

- Protocol: JSON-RPC 2.0 over Streamable HTTP
- Auth: `Authorization: Bearer <token>`
- Required scopes: `universal-mcp-read` or `universal-mcp-read-write`
- Rate limit: 30 requests/minute
- Response formats: JSON (`X-Response-Format: json`) or SSE streaming (`Accept: application/json, text/event-stream`)

### Supported Methods

| Method | Description | Scope Required |
|--------|-------------|----------------|
| `initialize` | Initialize MCP session | `universal-mcp-read` |
| `tools/list` | List available tools | `universal-mcp-read` |
| `tools/call` | Execute a tool | `universal-mcp-read-write` |

### Tool: use-agent

Executes an AI agent that accesses the user's connected services. The agent uses a ReAct pattern to reason about and execute requests across one or more services.

**Parameters:**
- `prompt` (string, required) - Natural language instruction
- `connector` (string, optional) - Filter to a specific service: `"google"`, `"slack"`, `"notion"`, `"github"`, `"jira"`, `"hubspot"`, `"stripe"`, etc.

**Request:**
```json
{
  "jsonrpc": "2.0",
  "method": "tools/call",
  "params": {
    "name": "use-agent",
    "arguments": {
      "prompt": "What's on my Google Calendar today?",
      "connector": "google"
    }
  },
  "id": "1"
}
```

**Response:**
```json
{
  "jsonrpc": "2.0",
  "id": "1",
  "result": {
    "content": [{ "type": "text", "text": "Here are your events for today:\n\n1. Team Standup - 9:00 AM\n2. Product Review - 2:00 PM" }]
  }
}
```

**Behavior:**
- Can coordinate across multiple services in a single call (e.g., "Get my calendar and post a summary to Slack")
- Responses are sanitized: sensitive tokens redacted, output truncated to 50KB
- Prompt injection detection blocks malicious inputs

### Tool: list-connections

Returns the user's active service connections.

**Request:**
```json
{
  "jsonrpc": "2.0",
  "method": "tools/call",
  "params": { "name": "list-connections", "arguments": {} },
  "id": "2"
}
```

**Response:**
```json
{
  "jsonrpc": "2.0",
  "id": "2",
  "result": {
    "content": [{ "type": "text", "text": "Active connections:\n- Google Workspace (user@gmail.com)\n- Slack (user@company.com)" }],
    "_meta": {
      "active": [
        {
          "serviceId": "conn-abc123",
          "serviceLabel": "Google Workspace",
          "connectedUserAccount": { "email": "user@gmail.com", "displayName": "John Doe" }
        }
      ]
    }
  }
}
```

### MCP Initialize

```json
{
  "jsonrpc": "2.0",
  "method": "initialize",
  "params": {
    "protocolVersion": "2025-03-26",
    "capabilities": {},
    "clientInfo": { "name": "my-app", "version": "1.0.0" }
  },
  "id": "1"
}
```

### Curl Examples

```bash
# List tools
curl -X POST https://api.bluenexus.ai/mcp \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -H "X-Response-Format: json" \
  -d '{"jsonrpc":"2.0","method":"tools/list","params":{},"id":"1"}'

# Call use-agent
curl -X POST https://api.bluenexus.ai/mcp \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -H "X-Response-Format: json" \
  -d '{"jsonrpc":"2.0","method":"tools/call","params":{"name":"use-agent","arguments":{"prompt":"What meetings do I have today?"}},"id":"2"}'
```

---

## Authentication

### Option 1: Personal Access Token (PAT)

For single-user and development use. Create at https://app.bluenexus.ai > Settings > Sessions.

```
Authorization: Bearer pat_xxxxxxxxxxxxxxxx
```

**Available scopes:**

| Scope | Access |
|-------|--------|
| `universal-mcp-read` | Discover MCP tools only |
| `universal-mcp-read-write` | Full MCP access (discover + execute) |
| `llm-all` | LLM chat completions |
| `agents-use` | Chat with existing agents |
| `agents-all` | Create/manage/delete agents (white-label only) |
| `connections` | Manage service connections |
| `account` | Read/update account details |
| `openid` | OpenID Connect identity |
| `profile` | User profile information |
| `email` | User email address |

**Create a PAT via API:**
```bash
curl -X POST https://api.bluenexus.ai/api/v1/auth/pat \
  -H "Authorization: Bearer SESSION_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "CI/CD Token",
    "scopes": ["universal-mcp-read-write", "llm-all"],
    "expires_in": 2592000,
    "provider_permissions": {
      "google": "read-write",
      "slack": "read"
    },
    "default_provider_permission": "read"
  }'
```

Response includes `personal_access_token` (shown only once).

### Option 2: OAuth 2.1 (multi-user apps)

For production apps where each end user has their own BlueNexus account. Uses PKCE (required).

**Step 1 - Register a client (Dynamic Client Registration, RFC 7591):**
```bash
curl -X POST https://api.bluenexus.ai/api/v1/auth/register \
  -H "Content-Type: application/json" \
  -d '{
    "client_name": "My App",
    "redirect_uris": ["https://myapp.com/callback"],
    "grant_types": ["authorization_code", "refresh_token"],
    "response_types": ["code"],
    "token_endpoint_auth_method": "none",
    "scope": "universal-mcp-read-write"
  }'
```
Response: `{ "client_id": "client_a1b2c3...", ... }`
Rate limit: 5 requests/minute per IP.

**Step 2 - Redirect user to authorize (with PKCE):**
```
https://app.bluenexus.ai/oauth/authorize?
  response_type=code&
  client_id=CLIENT_ID&
  redirect_uri=https://myapp.com/callback&
  scope=universal-mcp-read-write&
  state=random-csrf-token&
  code_challenge=CHALLENGE&
  code_challenge_method=S256
```

**Step 3 - Exchange code for tokens:**
```bash
curl -X POST https://api.bluenexus.ai/api/v1/auth/token \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d 'grant_type=authorization_code&code=AUTH_CODE&redirect_uri=https://myapp.com/callback&client_id=CLIENT_ID&code_verifier=VERIFIER'
```

Response:
```json
{
  "token_type": "Bearer",
  "access_token": "eyJ...",
  "expires_in": 900,
  "refresh_token": "eyJ...",
  "refresh_expires_in": 604800,
  "scope": "universal-mcp-read-write"
}
```

**Step 4 - Refresh tokens:**
```bash
curl -X POST https://api.bluenexus.ai/api/v1/auth/token \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d 'grant_type=refresh_token&refresh_token=REFRESH_TOKEN&client_id=CLIENT_ID'
```

Access tokens expire in ~15 minutes. Refresh tokens are single-use (each exchange returns a new one).

**Revoke a token:**
```bash
curl -X POST https://api.bluenexus.ai/api/v1/auth/revoke \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d 'token=TOKEN_TO_REVOKE&client_id=CLIENT_ID'
```

### OAuth Discovery (MCP clients)

MCP clients that don't have a pre-configured token discover authentication automatically:
1. `POST /mcp` without token returns `401` with `WWW-Authenticate: Bearer resource_metadata="https://api.bluenexus.ai/.well-known/oauth-protected-resource"`
2. Fetch `/.well-known/oauth-protected-resource` to discover authorization server
3. Fetch `/.well-known/oauth-authorization-server` to discover endpoints
4. `POST /v1/auth/register` (DCR) to get `client_id`
5. Standard PKCE OAuth flow
6. Retry `POST /mcp` with Bearer token

---

## REST API - Complete Endpoint Reference

Base URL: `https://api.bluenexus.ai/api/v1`
Content-Type: `application/json`
Auth: `Authorization: Bearer <token>`
Pagination: `?page=1&limit=20&sortBy=createdAt&sortOrder=desc`

**For full request/response schemas, see the Swagger UI: https://api.bluenexus.ai/api/docs**

### LLM Chat Completions (OpenAI-compatible)

```
POST /v1/chat/completions
```
Scope: `llm-all` | Rate limit: 30/min | Credit guard: yes

```json
{
  "model": "bluenexus/glm-4.7-flash-tee",
  "messages": [
    {"role": "system", "content": "You are helpful."},
    {"role": "user", "content": "Hello!"}
  ],
  "temperature": 0.7,
  "max_tokens": 500,
  "stream": false,
  "top_p": 1,
  "tools": [],
  "tool_choice": "auto",
  "response_format": {"type": "text"}
}
```

Response:
```json
{
  "id": "chatcmpl-abc123",
  "object": "chat.completion",
  "created": 1713000000,
  "model": "bluenexus/glm-4.7-flash-tee",
  "choices": [{
    "index": 0,
    "message": { "role": "assistant", "content": "Hello! How can I help?" },
    "finish_reason": "stop"
  }],
  "usage": { "prompt_tokens": 15, "completion_tokens": 8, "total_tokens": 23 }
}
```

Response headers: `X-Credits-Consumed`, `X-Credits-Remaining`.

Streaming (`"stream": true`) returns SSE with delta objects and `[DONE]` event.

**OpenAI SDK compatible:**
```python
from openai import OpenAI
client = OpenAI(api_key="YOUR_BLUENEXUS_TOKEN", base_url="https://api.bluenexus.ai/api/v1")
```

```typescript
import OpenAI from "openai";
const client = new OpenAI({ apiKey: "YOUR_BLUENEXUS_TOKEN", baseURL: "https://api.bluenexus.ai/api/v1" });
```

### List Models

```
GET /v1/models
```
Scope: `llm-all`

### Accounts

```
GET /v1/accounts/me
```
Scope: `account`

Response:
```json
{
  "id": "account-abc123",
  "name": "John Doe",
  "email": "john@example.com",
  "avatar": "https://...",
  "creditBalance": 18500,
  "createdAt": "2026-01-15T10:00:00Z",
  "updatedAt": "2026-04-13T08:00:00Z"
}
```

```
PUT /v1/accounts/me - Update account (name, avatar)
```

### Connections

```
GET /v1/connections - List user's connections
```
Scope: `connections` | Supports pagination

Response:
```json
{
  "data": [{
    "id": "conn-abc123",
    "providerId": "google",
    "providerUserId": "user@gmail.com",
    "scopes": ["calendar.read", "drive.read"],
    "status": "active",
    "canRefresh": true,
    "metadata": {
      "displayName": "John Doe",
      "email": "john@gmail.com",
      "providerName": "Google Workspace",
      "providerIcon": "/provider-logos/google.svg"
    },
    "createdAt": "2026-01-15T10:00:00Z"
  }],
  "pagination": { "page": 1, "limit": 20, "total": 3, "hasMore": false }
}
```

```
POST /v1/connections/initiate - Start OAuth flow for a new service
```

Request:
```json
{
  "providerId": "google",
  "redirectUrl": "https://yourapp.com/connections/success"
}
```

Response:
```json
{
  "authorizationUrl": "https://accounts.google.com/o/oauth2/v2/auth?...",
  "providerMetadata": { "name": "Google Workspace", "logoUrl": "...", "description": "..." }
}
```

Redirect the user to `authorizationUrl`. After they authorize, they'll be redirected back to your `redirectUrl`.

```
PUT /v1/connections/:id/refresh - Refresh expired connection tokens
DELETE /v1/connections/:id - Delete a connection (revokes access)
```

**Connection statuses:** `active`, `expired` (call refresh), `error` (may need reconnect), `revoked` (must reconnect)

### Providers (public - no auth required)

```
GET /v1/providers - List all available connectors
```

Response:
```json
[{
  "id": "google",
  "label": "Google Workspace",
  "description": "Gmail, Calendar, Drive, Docs, and more",
  "logoUrl": "/provider-logos/google.svg",
  "category": "productivity",
  "popular": true,
  "authType": "oauth",
  "allowMultipleConnections": false
}]
```

```
GET /v1/providers/:providerId - Get a specific provider
GET /v1/providers/:providerId/tools - List tools for a provider
```

Tool response:
```json
[{
  "name": "keyword_search",
  "description": "Search across the service",
  "parameters": { "type": "object", "properties": { "query": { "type": "string" } }, "required": ["query"] },
  "mode": "read"
}]
```

### Agents

```
POST /v1/agents - Create an agent
```
Scope: `agents-all`

Request:
```json
{
  "name": "Support Agent",
  "llm": "bluenexus/glm-4.7-flash-tee",
  "personality": "You are a helpful support agent.",
  "description": "Handles support queries",
  "temperature": 0.7,
  "firstPrompt": "Hello! How can I help?",
  "enabledBuiltInTools": ["provider-tools", "skills"],
  "billingAccountType": "owner",
  "usageLimits": {
    "dailyMessagesPerUser": 100,
    "monthlyCreditsCap": 5000,
    "limitReachedMessage": "Limit reached."
  }
}
```

Built-in tools: `provider-tools` (connected services), `skills` (reusable behaviors), `agent-memory` (long-term memory), `account-tasks` (task management), `execute-code` (code sandbox).

```
GET /v1/agents - List agents (agents-all or agents-use scope)
GET /v1/agents/:agentId - Get agent (use "default" for account default)
PUT /v1/agents/:agentId - Update agent (agents-all scope)
DELETE /v1/agents/:agentId - Delete agent (agents-all scope)
```

**Note:** Agents can also be created and managed through the visual Agent Builder UI at https://app.bluenexus.ai - no code required.

### Agent Chat

```
POST /v1/agents/:agentId/chat/completions
```
Scope: `agents-all` or `agents-use` | Rate limit: 30/min | Credit guard: yes

Request:
```json
{
  "messages": [{"role": "user", "content": "What's on my calendar today?"}],
  "stream": false,
  "conversationId": "conv-xyz"
}
```

Response: OpenAI-compatible chat completion format.
Response headers: `X-Credits-Consumed`, `X-Credits-Remaining`, `X-Conversation-Id`.
Streaming (`"stream": true`) returns SSE with delta objects.

### Scheduled Tasks

```
POST /v1/agents/:agentId/scheduled-tasks - Create scheduled task
```
Scope: `agents-all`

Request:
```json
{
  "name": "Daily Summary",
  "prompt": "Summarize yesterday's Slack activity and post to #standup",
  "cronExpression": "0 9 * * 1-5",
  "timezone": "America/New_York",
  "enabled": true
}
```

Cron format: `minute hour day-of-month month day-of-week`

```
GET /v1/agents/:agentId/scheduled-tasks - List tasks
PUT /v1/agents/:agentId/scheduled-tasks/:taskId - Update task
DELETE /v1/agents/:agentId/scheduled-tasks/:taskId - Delete task
```

### Auth Clients

```
POST /v1/auth-clients - Create OAuth client
```

Request:
```json
{
  "name": "My App",
  "type": "confidential",
  "roles": ["third-party"],
  "redirectUris": ["https://myapp.com/callback"],
  "allowedScopes": ["universal-mcp-read-write", "agents-use", "llm-all"],
  "branding": {
    "logo": "data:image/png;base64,...",
    "description": "AI productivity app",
    "brandColor": "#4F46E5"
  }
}
```

Client types: `public` (SPAs/mobile, can't store secrets) or `confidential` (server-side).
Roles: `third-party` (standard) or `whitelabel-customer` (custom branding, by invitation only).

```
GET /v1/auth-clients - List clients
PUT /v1/auth-clients/:clientId - Update client
DELETE /v1/auth-clients/:clientId - Delete client
PUT /v1/auth-clients/:clientId/secret - Rotate client secret
```

### Credit Transactions

```
GET /v1/credit-transactions - Paginated credit history
```

### Authorization Scopes

```
GET /v1/auth-scopes - List all available scopes with descriptions
```

---

## Framework Integration Examples

### MCP Client Config (Claude Desktop, Claude Code, any MCP client)
```json
{
  "mcpServers": {
    "bluenexus": {
      "type": "streamable-http",
      "url": "https://api.bluenexus.ai/mcp",
      "headers": {
        "Authorization": "Bearer YOUR_TOKEN"
      }
    }
  }
}
```

### Python - LangChain
```python
import asyncio
from langchain_mcp_adapters.tools import load_mcp_tools
from langchain_openai import ChatOpenAI
from langgraph.prebuilt import create_react_agent
from mcp import ClientSession
from mcp.client.streamable_http import streamablehttp_client

TOKEN = "your-personal-access-token"

async def main():
    async with streamablehttp_client("https://api.bluenexus.ai/mcp", headers={"Authorization": f"Bearer {TOKEN}"}) as (r, w, _):
        async with ClientSession(r, w) as session:
            await session.initialize()
            tools = await load_mcp_tools(session)
            agent = create_react_agent(ChatOpenAI(model="gpt-4o"), tools)
            result = await agent.ainvoke({"messages": [{"role": "user", "content": "What's on my Google Calendar today?"}]})
            print(result["messages"][-1].content)

asyncio.run(main())
```

### Python - CrewAI
```python
import asyncio
from crewai import Agent, Task, Crew
from crewai_tools import MCPServerAdapter

TOKEN = "your-personal-access-token"

async def main():
    server = MCPServerAdapter(server_url="https://api.bluenexus.ai/mcp", headers={"Authorization": f"Bearer {TOKEN}"})
    await server.setup()
    agent = Agent(role="Assistant", goal="Help user", tools=server.tools, llm="gpt-4o")
    task = Task(description="What's on my Google Calendar today?", agent=agent, expected_output="Calendar summary")
    crew = Crew(agents=[agent], tasks=[task])
    result = await crew.kickoff_async()
    print(result)

asyncio.run(main())
```

### TypeScript - Vercel AI SDK
```typescript
import { generateText } from "ai";
import { openai } from "@ai-sdk/openai";
import { experimental_createMCPClient } from "ai";
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";

const TOKEN = "your-personal-access-token";

const client = await experimental_createMCPClient({
  transport: new StreamableHTTPClientTransport(new URL("https://api.bluenexus.ai/mcp"), {
    requestInit: { headers: { Authorization: `Bearer ${TOKEN}` } },
  }),
});

const tools = await client.tools();
const { text } = await generateText({
  model: openai("gpt-4o"),
  tools,
  prompt: "What's on my Google Calendar today?",
  maxSteps: 5,
});
console.log(text);
await client.close();
```

### TypeScript - OpenAI Agents SDK
```typescript
import { Agent, run } from "@openai/agents";
import { MCPServerStreamableHttp } from "@openai/agents-mcp";

const TOKEN = "your-personal-access-token";

const server = new MCPServerStreamableHttp({
  url: "https://api.bluenexus.ai/mcp",
  headers: { Authorization: `Bearer ${TOKEN}` },
});

const agent = new Agent({
  name: "Assistant",
  instructions: "Help the user with their connected services.",
  mcpServers: [server],
});

const result = await run(agent, "What's on my Google Calendar today?");
console.log(result.finalOutput);
```

### Raw MCP SDK (TypeScript)
```typescript
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";

const TOKEN = "your-personal-access-token";

const client = new Client({ name: "my-app", version: "1.0.0" });
const transport = new StreamableHTTPClientTransport(new URL("https://api.bluenexus.ai/mcp"), {
  requestInit: { headers: { Authorization: `Bearer ${TOKEN}` } },
});
await client.connect(transport);

const { tools } = await client.listTools();
const result = await client.callTool("use-agent", { prompt: "What's on my Google Calendar today?" });
console.log(result.content[0].text);

await client.close();
```

### Python - OpenAI SDK (direct LLM, no MCP)
```python
from openai import OpenAI

client = OpenAI(api_key="YOUR_BLUENEXUS_TOKEN", base_url="https://api.bluenexus.ai/api/v1")
response = client.chat.completions.create(
    model="bluenexus/glm-4.7-flash-tee",
    messages=[{"role": "user", "content": "Hello!"}]
)
print(response.choices[0].message.content)
```

---

## Error Handling

### Error Response Format
```json
{
  "error": "ErrorName",
  "message": "Human-readable description",
  "statusCode": 401
}
```

### HTTP Status Codes

| Code | Error | Description | Action |
|------|-------|-------------|--------|
| 400 | `BadRequestError` | Invalid request format or parameters | Fix request |
| 401 | `UnauthorizedError` | Missing or invalid Bearer token | Refresh token or re-authenticate |
| 401 | `TokenExpiredError` | Access token expired | Use refresh token |
| 402 | `CreditsExhaustedError` | No remaining credits | Do not retry |
| 403 | `ForbiddenError` | Valid auth but insufficient scopes | Request additional scopes |
| 404 | `NotFoundError` | Resource doesn't exist | Check ID |
| 422 | `ValidationError` | Validation failed | Check parameters |
| 429 | `TooManyRequestsError` | Rate limit exceeded | Retry after `Retry-After` header |
| 500 | `InternalServerError` | Server error | Retry with backoff (max 3) |

### MCP-Specific Errors

| Error | Description |
|-------|-------------|
| `McpToolUnauthorizedError` | No valid authentication for tool execution |
| `McpToolInsufficientCreditsError` | Insufficient credits for tool execution |
| `McpToolPromptInjectionBlockedError` | Prompt injection detected and blocked |
| `McpToolAgentExecutionError` | Agent execution failed |

### OAuth Errors (returned on auth endpoints)

| Error | Description |
|-------|-------------|
| `invalid_client` | Client not found or invalid credentials |
| `invalid_request` | Missing required parameter |
| `invalid_grant` | Authorization code invalid, expired, or already used |
| `unsupported_grant_type` | Grant type not supported |
| `invalid_scope` | Requested scope not allowed |

### Error Handling Example

```javascript
async function callBlueNexusApi(url, options) {
  const response = await fetch(url, options);

  if (response.status === 401) {
    const newToken = await refreshToken();
    options.headers.Authorization = `Bearer ${newToken}`;
    return fetch(url, options);
  }

  if (response.status === 429) {
    const retryAfter = response.headers.get("Retry-After") || 60;
    await new Promise(r => setTimeout(r, retryAfter * 1000));
    return fetch(url, options);
  }

  if (response.status === 402) {
    throw new Error("Credits exhausted. Do not retry.");
  }

  if (!response.ok) {
    const error = await response.json();
    throw new Error(`${error.error}: ${error.message}`);
  }

  return response;
}
```

---

## Rate Limits

| Endpoint | Limit | Window |
|----------|-------|--------|
| Global default | 100 req | 1 minute |
| `POST /mcp` | 30 req | 1 minute |
| `POST /v1/chat/completions` | 30 req | 1 minute |
| `POST /v1/agents/:id/chat/completions` | 30 req | 1 minute |
| `POST /v1/auth/token` | 10 req | 1 minute |
| `POST /v1/auth/revoke` | 10 req | 1 minute |
| `POST /v1/auth/register` (DCR) | 5 req | 1 minute |

Credit-guarded endpoints (`/mcp`, `/chat/completions`, agent chat) return `402` when credits reach zero.

---

## Credits

- 1 credit = $0.0001 USD
- New accounts start with 100,000 credits ($10.00)
- Consumed by: LLM token usage, agent tool execution
- Check balance: `X-Credits-Remaining` response header or `GET /v1/accounts/me` (field: `creditBalance`)
- Zero balance: `402 CreditsExhaustedError` on credit-guarded endpoints
- Transaction history: `GET /v1/credit-transactions`
