NullSpend Docs

Tags

Tags let you attribute costs to teams, environments, features, or anything else. Attach a JSON object to any request and query costs by those dimensions in the

Tags let you attribute costs to teams, environments, features, or anything else. Attach a JSON object to any request and query costs by those dimensions in the dashboard, API, or webhooks.

Sending Tags

Add the X-NullSpend-Tags header with a JSON object:

TypeScript

const response = await openai.chat.completions.create(
  {
    model: "gpt-4o",
    messages: [{ role: "user", content: "Hello" }],
  },
  {
    headers: {
      "X-NullSpend-Tags": JSON.stringify({
        team: "billing",
        env: "production",
        feature: "summarizer",
      }),
    },
  }
);

Python

import json

response = client.chat.completions.create(
    model="gpt-4o",
    messages=[{"role": "user", "content": "Hello"}],
    extra_headers={
        "X-NullSpend-Tags": json.dumps({
            "team": "billing",
            "env": "production",
            "feature": "summarizer",
        }),
    },
)

cURL

curl https://proxy.nullspend.dev/v1/chat/completions \
  -H "Authorization: Bearer $OPENAI_API_KEY" \
  -H "X-NullSpend-Key: $NULLSPEND_API_KEY" \
  -H 'X-NullSpend-Tags: {"team":"billing","env":"production"}' \
  -H "Content-Type: application/json" \
  -d '{"model": "gpt-4o", "messages": [{"role": "user", "content": "Hello"}]}'

Validation Rules

Tags are supplementary — they never cause a request to be rejected. Invalid tags are silently dropped.

RuleLimit
Max keys per request10
Key pattern[a-zA-Z0-9_-]+
Key max length64 characters
Value max length256 characters
Reserved prefix_ns_ — keys starting with this are silently dropped
Null bytes in valuesTag is silently dropped
Invalid JSONAll tags silently dropped (request proceeds with no tags)
Single invalid key/valueThat key is dropped; valid keys are kept

System Tags

NullSpend uses the _ns_ prefix for internal tags. You cannot set these — they are added automatically when applicable. User-supplied tags with the _ns_ prefix are silently dropped.

All providers

TagValueWhen
_ns_estimated"true"Cost is an estimate (stream was cancelled before completion or no usage was reported)
_ns_cancelled"true"The streaming response was cancelled by the client
_ns_no_usage"true"The provider returned a response without usage metadata
_ns_unpriced"true"The model isn't in the pricing catalog — cost recorded as 0
_ns_max_tokensstringThe max_tokens / maxOutputTokens value sent on the request
_ns_temperaturestringThe temperature value sent on the request
_ns_tool_countstringNumber of tool definitions in the request

OpenAI / Anthropic

TagValueWhen
_ns_ratelimit_remaining_requestsstringProvider-reported requests-remaining (from x-ratelimit-remaining-requests)
_ns_ratelimit_remaining_tokensstringProvider-reported tokens-remaining (from x-ratelimit-remaining-tokens)

Anthropic only

TagValueWhen
_ns_cache_write_tokensstringTokens written to the prompt cache (cache_creation_input_tokens)
_ns_cache_read_tokensstringTokens read from the prompt cache (cache_read_input_tokens)
_ns_long_context"true"Total input exceeded 200K tokens — long-context multipliers applied

Gemini only

TagValueWhen
_ns_thinking_tokensstringThinking tokens consumed (thoughtsTokenCount, Gemini 2.5+)
_ns_google_response_idstringGoogle's responseId from the response body (proxy generates its own x-request-id header)

Customer Attribution

For per-customer cost tracking and margin analysis, use the dedicated X-NullSpend-Customer header instead of (or in addition to) tags. The customer ID lands in the customer_id column of cost events, which the Margins dashboard and the Margins API read from directly.

If you cannot set a custom header (e.g., a third-party SDK that strips unknown headers), use the customer tag fallback:

X-NullSpend-Tags: {"customer":"acme-corp"}

The proxy auto-elevates the customer tag value into the customer_id column, so margin tracking works the same either way. The header takes precedence when both are present.

Cost Attribution by Tags

The Attribution page lets you group costs by any tag value. Select a tag key from the dropdown and see a ranked breakdown of spend per value — with daily trends, model breakdowns, and CSV export.

This is the primary way to answer "how much does each customer cost me?" when you're tagging requests with customer_id or similar keys.

Querying by Tags

Dashboard

Filter cost events by tag key-value pairs in the analytics view, or use the Attribution page for aggregated per-tag-value breakdowns.

API

Use tag.* query parameters on GET /api/cost-events:

# All cost events tagged with team=billing (requires dashboard session)
curl "https://nullspend.dev/api/cost-events?tag.team=billing" \
  -H "Cookie: session=..."

# Multiple tag filters (AND logic)
curl "https://nullspend.dev/api/cost-events?tag.team=billing&tag.env=production" \
  -H "Cookie: session=..."

Tag queries use PostgreSQL JSONB containment (@>), so they are indexed and fast.

Tags in Webhooks

Tags are included in the cost_event.created webhook payload under data.object.tags:

{
  "id": "evt_abc123",
  "type": "cost_event.created",
  "api_version": "2026-04-01",
  "created_at": 1711036800,
  "data": {
    "object": {
      "request_id": "req_xyz",
      "provider": "openai",
      "model": "gpt-4o",
      "cost_microdollars": 45,
      "tags": {
        "team": "billing",
        "env": "production"
      }
    }
  }
}

See Webhook Event Types for the full payload.

Tag Budgets

You can create budgets scoped to a specific tag key-value pair. When spend for that tag exceeds the budget, the proxy blocks requests carrying that tag with 429:

{
  "error": {
    "code": "tag_budget_exceeded",
    "message": "Request blocked: tag budget exceeded",
    "details": {
      "tag_key": "team",
      "tag_value": "billing",
      "budget_limit_microdollars": 50000000,
      "budget_spend_microdollars": 49500000
    }
  }
}

Tag budgets support the same features as user and API key budgets: reset intervals, threshold alerts, and velocity limits. See Budgets for configuration details.

On this page