Billing & Credits
The isA Model Service includes a complete billing infrastructure for tracking API usage, enforcing credit limits, and attributing costs across projects and API keys.
Overview
All API calls consume credits. Credits are deducted atomically at request time via a Redis reservation, then finalized after execution. This ensures accurate accounting even under concurrent load and prevents overspending.
Request arrives
│
▼
Atomic Redis reservation (Lua script)
│
├── Insufficient credits → 402 Payment Required
│
▼
Execute request
│
▼
Finalize usage → emit NATS billing event → update balance
│
▼
Credit alert (if balance < alert_threshold_credits)Credits
Credits are the billing unit for all isA platform services.
| Conversion | Rate |
|---|---|
| 1 credit | $0.001 USD |
| 1,000 credits | $1.00 USD |
Credits are pre-purchased and drawn down as you use the platform. When credits run out, requests are rejected with 402 Payment Required unless overage is configured.
Meter Types
Every billable operation is tracked against a MeterType:
| MeterType | Triggered by | Unit |
|---|---|---|
TOKEN_INPUT | Input tokens sent to model | per 1K tokens |
TOKEN_OUTPUT | Output tokens generated | per 1K tokens |
TOOL_CALL | MCP tool execution | per call |
STORAGE_READ | MinIO/storage reads | per MB |
STORAGE_WRITE | MinIO/storage writes | per MB |
PIPELINE_RUN | isA_Data pipeline execution | per run |
Example costs (illustrative — check your plan for actual rates):
| MeterType | Example cost |
|---|---|
TOKEN_INPUT | 0.15 credits / 1K tokens |
TOKEN_OUTPUT | 0.60 credits / 1K tokens |
TOOL_CALL | 0.5 credits / call |
STORAGE_READ | 0.01 credits / MB |
STORAGE_WRITE | 0.02 credits / MB |
PIPELINE_RUN | 1.0 credits / run |
Credit Alerts
Configure a threshold below which a NATS alert is published:
# Environment variable
BILLING_ALERT_THRESHOLD_CREDITS=100Alert events are published to the billing.alerts.{api_key} NATS topic:
{
"event": "low_balance",
"api_key": "isa_live_abc123",
"balance_credits": 87.4,
"threshold_credits": 100,
"timestamp": "2026-03-27T12:00:00Z"
}Subscribe to alerts in your application:
import nats
nc = await nats.connect("nats://localhost:4222")
sub = await nc.subscribe("billing.alerts.isa_live_abc123")
async for msg in sub.messages:
alert = json.loads(msg.data)
if alert["event"] == "low_balance":
await notify_team(f"Credits low: {alert['balance_credits']} remaining")Overage Configuration
By default, requests fail when credits reach zero. Configure an overage allowance to continue beyond zero (billed at the end of the period):
BILLING_OVERAGE_LIMIT_CREDITS=500 # Allow up to 500 credits of overageWhen overage is active, responses include a header:
X-ISA-Overage-Credits: 42.3Dead Letter Queue
Billing events that fail to persist (e.g., during a database outage) are written to the billing-dlq-stream NATS JetStream:
# Inspect DLQ
nats stream info billing-dlq-stream
# Replay failed events (admin only)
nats consumer create billing-dlq-stream --deliver allCost Attribution
Costs are attributed to both a project_id and an api_key. Pass X-Project-ID in requests to enable project-level reporting:
curl -X POST http://localhost:8082/api/v1/invoke \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "X-Project-ID: proj_abc123" \
-d '{"model": "gpt-4o-mini", "messages": [...]}'See Metering & Usage API for querying cost breakdown by project and API key.
Multi-Tenant Billing Scope (SaaS Deployments)
In multi-tenant deployments (e.g. isagent.io), a single API key can act on behalf of many users across many orgs. Every billable request must resolve to exactly one ledger — the requesting user’s personal balance, their org’s shared balance, or (for system-initiated calls) a system ledger — before it’s charged.
This resolution is carried in a signed billing_scope JWT claim, minted upstream by the identity/session layer (isA_user) and forwarded on every request that reaches the Model Service:
{
"billing_scope": {
"ledger": "org",
"org_id": "org_abc123",
"user_id": "user_def456"
}
}The Model Service validates the claim’s signature and charges the ledger it names — it does not infer scope from the API key or request context alone. This closed a real production bug: previously, any request the SDK couldn’t confidently attribute to a user fell back to charging the org’s payer pool, silently draining it even for individual-user activity.
Correct billing_scope handling requires isa-model 0.6.2 or later. Earlier versions (and deployments with a stale SDK pin) skip claim validation and fall back to the pre-fix org-payer-pool behavior — if you see unexplained org-pool drain, check your isa-model version first.
Billing Hooks (Internal Services)
Other isA services report usage to the Model Service via internal billing hooks. These endpoints are not for external use.
| Service | Endpoint | Triggered by |
|---|---|---|
| isA_MCP | POST /api/v1/tools/usage | Every MCP tool execution |
| isA_OS | POST /api/v1/storage/usage | Every MinIO read/write |
| isA_Data | POST /api/v1/pipelines/usage | Every pipeline run |
Environment Variables
| Variable | Description | Default |
|---|---|---|
BILLING_ENABLED | Enable/disable billing | true |
BILLING_ALERT_THRESHOLD_CREDITS | Low-balance alert threshold | 100 |
BILLING_OVERAGE_LIMIT_CREDITS | Overage allowance (0 = no overage) | 0 |
BILLING_PRICING_CACHE_TTL_SECS | Pricing cache duration | 300 |
NATS_URL | NATS server for billing events | nats://localhost:4222 |
REDIS_URL | Redis for atomic reservations | redis://localhost:6379 |
Next Steps
- Metering & Usage API — query usage and cost breakdowns
- RBAC — control who can access billing endpoints
- Audit Logging — immutable log of all billing events