NullSpend Docs

Errors

Every error response from NullSpend — both the proxy and the dashboard API — uses the same format:

Every error response from NullSpend — both the proxy and the dashboard API — uses the same format:

{
  "error": {
    "code": "budget_exceeded",
    "message": "Request blocked: estimated cost exceeds remaining budget",
    "details": null
  }
}
  • code — Machine-readable error identifier. Use this for programmatic handling.
  • message — Human-readable explanation.
  • details — Additional context (object or null). Present on validation errors, session limits, and velocity limits.

Proxy Errors

These errors are returned by the NullSpend proxy (proxy.nullspend.dev).

Authentication

CodeHTTPWhenFix
unauthorized401X-NullSpend-Key header is missing, malformed, or the key has been revokedVerify the header is present and the key is active in Settings

Budget Enforcement

CodeHTTPWhenFix
budget_exceeded429Estimated cost of this request exceeds the remaining budgetIncrease the budget ceiling, wait for the period to reset, or remove the budget
velocity_exceeded429Spend rate within the velocity window exceeds the configured limitWait for the cooldown period (check Retry-After header). The response details includes limitMicrodollars, windowSeconds, and currentMicrodollars
session_limit_exceeded429Cumulative spend for this session ID exceeds the session limitStart a new session (new X-NullSpend-Session value) or increase the session limit. The response details includes session_id, session_spend_microdollars, and session_limit_microdollars
tag_budget_exceeded429Estimated cost exceeds a tag-level budget limitAdjust the tag budget. The response details includes tag_key, tag_value, budget_limit_microdollars, and budget_spend_microdollars
budget_unavailable503Budget enforcement service is temporarily unavailableRetry after a brief delay. The proxy fails closed — requests are blocked, not passed through

Request Validation

CodeHTTPWhenFix
bad_request400Request body is not valid JSON or is missing required fieldsCheck the request body format
invalid_model400The model field is not in the pricing catalogCheck supported models
payload_too_large413Request body exceeds 1 MBReduce the request body size
invalid_upstream400X-NullSpend-Upstream URL is not in the allowlistUse the default upstream or contact support to add your URL

Rate Limiting

CodeHTTPWhenFix
rate_limited429Too many requests. Default limits: 120/min per IP, 600/min per API keyReduce request rate. Check the Retry-After header for when to retry

Rate limit responses include X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset, and Retry-After headers. See the headers reference. For the full rate limiting reference, see Rate Limits.

Upstream & Server

CodeHTTPWhenFix
upstream_error502The upstream provider returned an error or no response bodyCheck the provider's status page (e.g., status.openai.com)
not_found404The requested endpoint is not supported by the proxySupported endpoints: POST /v1/chat/completions (OpenAI), POST /v1/messages (Anthropic), POST /v1/mcp/budget/check, POST /v1/mcp/events
internal_error500Unexpected server errorRetry the request. If persistent, contact support with the X-NullSpend-Trace-Id from the response

Dashboard API Errors

These errors are returned by the NullSpend dashboard API (nullspend.dev/api/).

Validation

CodeHTTPWhenFix
invalid_json400Request body is not valid JSONSend valid JSON with Content-Type: application/json
validation_error400Request failed schema validationCheck details.issues for specific field errors
unsupported_media_type415Content-Type is not application/jsonSet the Content-Type header
payload_too_large413Request body exceeds the max sizeReduce the request body

Validation error details include an issues array:

{
  "error": {
    "code": "validation_error",
    "message": "Request validation failed.",
    "details": {
      "issues": [
        { "path": ["amount"], "message": "Expected number, received string" }
      ]
    }
  }
}

Resources

CodeHTTPWhenFix
not_found404The requested resource does not existVerify the resource ID
limit_exceeded409Resource limit reached for the organization's tier (e.g., Free: 10 keys, 2 webhooks, 3 budgets)Delete unused resources or upgrade to a higher tier

HITL Actions

CodeHTTPWhenFix
invalid_action_transition409Invalid state transition (e.g., approving an already-rejected action)Check the action's current state before transitioning
stale_action409The action was modified by another actor since you last fetched itRe-fetch the action and retry
action_expired409The action's TTL has expiredCreate a new action

Rate Limiting

CodeHTTPWhenFix
rate_limit_exceeded429Too many requests (per-IP or per-key)Reduce request rate. Check the Retry-After header

Note: The proxy uses rate_limited while the dashboard API uses rate_limit_exceeded. Handle both codes if your application calls both services.

Authentication & Authorization

CodeHTTPWhenFix
authentication_required401No valid session or API keyLog in or provide a valid X-NullSpend-Key header
forbidden403Authenticated but not authorized to access this resourceVerify you own the resource

Server

CodeHTTPWhenFix
service_unavailable503A downstream service is temporarily unavailableRetry after a brief delay
internal_error500Unexpected server errorRetry the request

HTTP Status Code Summary

StatusMeaning
400Bad request — check your input
401Identity unknown — check your API key or session
403Identity known but not authorized
404Resource or endpoint not found
409Conflict — resource limit or state conflict
413Request body too large
415Wrong content type
429Rate or budget limit exceeded — check Retry-After
500Server error — retry
502Upstream provider error
503Service temporarily unavailable — retry

Handling Errors Programmatically

const response = await fetch("https://proxy.nullspend.dev/v1/chat/completions", {
  method: "POST",
  headers: {
    "Authorization": `Bearer ${process.env.OPENAI_API_KEY}`,
    "X-NullSpend-Key": process.env.NULLSPEND_API_KEY,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({ model: "gpt-4o", messages: [{ role: "user", content: "Hello" }] }),
});

if (!response.ok) {
  const { error } = await response.json();
  switch (error.code) {
    case "budget_exceeded":
      // Notify the user, queue for later, or request budget increase
      break;
    case "velocity_exceeded":
      // Back off and retry after Retry-After seconds
      const retryAfter = response.headers.get("Retry-After");
      break;
    case "rate_limited":
      // Reduce request rate
      break;
    default:
      console.error(`NullSpend error: ${error.code} — ${error.message}`);
  }
}

API Reference

On this page