NullSpend Docs

API Reference Overview

NullSpend exposes a REST API for cost tracking, budget management, and human-in-the-loop workflows. All endpoints live under `https://nullspend.dev/api/`.

NullSpend exposes a REST API for cost tracking, budget management, and human-in-the-loop workflows. All endpoints live under https://nullspend.dev/api/.


Authentication

NullSpend uses two authentication modes depending on the endpoint. For key lifecycle and caching details, see Authentication.

API Key (agent/SDK use)

Pass your API key in the X-NullSpend-Key header:

X-NullSpend-Key: ns_live_sk_...

API keys are created in the dashboard and identified by a ns_key_ prefix. The raw key is shown only once at creation. See API Keys API for management endpoints.

Session (dashboard use)

Session-authenticated endpoints require a browser session cookie from the NullSpend dashboard. These endpoints power the dashboard UI and are not callable from external scripts without a valid session.

Endpoint Auth Summary

EndpointMethodAuth
POST /api/cost-eventsIngest eventAPI key
POST /api/cost-events/batchBatch ingestAPI key
GET /api/cost-eventsList eventsSession
GET /api/cost-events/:idGet eventSession
GET /api/cost-events/sessions/:sessionIdGet sessionSession
GET /api/cost-events/summaryAnalyticsSession
GET /api/budgets/statusBudget statusAPI key
GET /api/auth/introspectKey introspectionAPI key
POST /api/actionsCreate actionAPI key
POST /api/actions/:id/resultMark resultAPI key
GET /api/actions/:idGet actionAPI key or Session
GET /api/actions/:id/costsAction costsAPI key or Session
GET /api/budgetsList budgetsSession
POST /api/budgetsCreate/update budgetSession
DELETE /api/budgets/:idDelete budgetSession
POST /api/budgets/:idReset budget spendSession
GET /api/budgets/velocity-statusVelocity stateSession
GET /api/keysList keysSession
POST /api/keysCreate keySession
DELETE /api/keys/:idRevoke keySession
POST /api/actions/:id/approveApprove actionSession
POST /api/actions/:id/rejectReject actionSession
GET /api/actionsList actionsSession
GET /api/webhooksList webhooksSession
POST /api/webhooksCreate webhookSession
PATCH /api/webhooks/:idUpdate webhookSession
DELETE /api/webhooks/:idDelete webhookSession
POST /api/webhooks/:id/testTest webhookSession
POST /api/webhooks/:id/rotate-secretRotate secretSession
GET /api/webhooks/:id/deliveriesDelivery historySession
GET /api/orgsList user's orgsSession
POST /api/orgsCreate team orgSession
GET /api/orgs/:orgIdGet org detailsSession (member+)
PATCH /api/orgs/:orgIdUpdate orgSession (admin+)
DELETE /api/orgs/:orgIdDelete orgSession (owner)
GET /api/orgs/:orgId/membersList membersSession (member+)
PATCH /api/orgs/:orgId/members/:userIdChange roleSession (admin+)
DELETE /api/orgs/:orgId/members/:userIdRemove memberSession (admin+)
GET /api/orgs/:orgId/invitationsList invitationsSession (admin+)
POST /api/orgs/:orgId/invitationsInvite memberSession (admin+)
DELETE /api/orgs/:orgId/invitations/:idRevoke invitationSession (admin+)
POST /api/invite/acceptAccept invitationSession
GET /api/cost-events/attributionCost attributionSession (viewer)
GET /api/cost-events/attribution/:keyAttribution detailSession (viewer)
GET /api/cost-events/tag-keysDistinct tag keysSession (viewer)
GET /api/cost-events/exportCSV exportSession (viewer)
GET /api/cost-events/:id/bodiesRequest/response bodiesSession (viewer)
GET /api/audit-logAudit logSession (admin)
GET /api/tool-costsList tool costsAPI key or Session (viewer)
POST /api/tool-costsCreate/update tool costSession (admin)
DELETE /api/tool-costs/:idReset tool costSession (admin)
POST /api/tool-costs/discoverDiscover toolsAPI key
PATCH /api/keys/:idUpdate keySession (admin)
POST /api/orgs/:orgId/leaveLeave orgSession (member)
POST /api/orgs/:orgId/transferTransfer ownershipSession (owner)
GET /api/auth/sessionCurrent sessionSession
POST /api/auth/switch-orgSwitch active orgSession
GET /api/slack/configGet Slack configSession (admin)
POST /api/slack/configSet Slack configSession (admin)
POST /api/slack/testTest Slack webhookSession (admin)
POST /api/stripe/checkoutCreate checkoutSession (owner)
POST /api/stripe/portalBilling portalSession (owner)
GET /api/stripe/subscriptionGet subscriptionSession (viewer)
POST /api/stripe/subscription/syncSync subscriptionSession
GET /api/healthHealth checkPublic

ID Formats

All NullSpend IDs use the ns_ prefix followed by a type identifier and a UUID:

PrefixResource
ns_act_Action
ns_key_API key
ns_evt_Cost event
ns_bgt_Budget
ns_wh_Webhook endpoint
ns_del_Webhook delivery
ns_usr_User
ns_tc_Tool cost
ns_org_Organization

Error Format

All errors follow a consistent shape:

{
  "error": {
    "code": "machine_readable_code",
    "message": "Human-readable description.",
    "details": null
  }
}

Validation errors include field-level issues in details:

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

See Error Reference for the full error catalog.


Pagination

List endpoints use cursor-based pagination. The response includes a cursor field — pass it as the cursor query parameter to fetch the next page.

{
  "data": [...],
  "cursor": { "createdAt": "2026-03-15T10:00:00.000Z", "id": "ns_evt_..." }
}
  • limit controls page size (default varies by endpoint, max 100)
  • cursor is a JSON-encoded object containing createdAt and id
  • When cursor is null, there are no more results
  • Pagination is forward-only, sorted by createdAt DESC

Rate Limiting

For enforcement order and failure modes, see Rate Limits.

API-key-authenticated endpoints return rate limit headers:

X-RateLimit-Limit: 100
X-RateLimit-Remaining: 97
X-RateLimit-Reset: 1711036800000

When the limit is exceeded, the endpoint returns 429 Too Many Requests with a Retry-After header (in seconds).


Idempotency

POST endpoints that accept API key auth support idempotent requests. Pass an Idempotency-Key header or include idempotencyKey in the request body. Duplicate requests return 200 with the original result instead of creating a new resource.


Versioning

For the version resolution chain and strategy, see Versioning.

Responses include a NullSpend-Version header indicating the API version:

NullSpend-Version: 2026-04-01

On this page