Stack SpendDocs

API Reference

Use the StackSpend REST API to pull cost line items, pre-aggregated rollups, and anomaly alerts into your own tooling.

Interactive API explorer.Prefer to try requests live? Open the interactive API explorer (Swagger UI) — browse every endpoint, see schemas, and run authenticated requests with your API key. Powered by our OpenAPI spec.

Overview

The StackSpend API is a REST API hosted at https://api.stackspend.app. All endpoints are versioned under /api/v1/.

Every response is wrapped in a standard envelope:

{
  "data":  { ... },   // payload — object or array
  "error": null,      // null on success; error object on failure
  "meta":  { ... }    // pagination cursors, request metadata
}
Business plan required.API access is available on the Business plan and above. Requests from free or Starter plan workspaces will receive a PLAN_RESTRICTED error.

Rate limits

The default limit is 60 requests per minute per API key. Exceeding this returns a 429 RATE_LIMITED error. Contact support@stackspend.com to request a higher limit.

Authentication

Authenticate by passing your API key in the X-API-Key header, or as a Bearer token in the Authorization header. Both forms are accepted.

# Using X-API-Key header
curl -H "X-API-Key: your_key" \
  "https://api.stackspend.app/api/v1/public/line-items?start_date=2025-01-01&end_date=2025-01-31"

# Using Authorization: Bearer
curl -H "Authorization: Bearer your_key" \
  "https://api.stackspend.app/api/v1/public/line-items?start_date=2025-01-01&end_date=2025-01-31"

Creating API keys

Go to Settings → API in your StackSpend workspace, then click Create key. The secret is shown once at creation — copy it immediately and store it securely. You cannot retrieve it again after closing the dialog.

Scopes

Each key carries one or more scopes. Calling an endpoint without the required scope returns INVALID_SCOPE.

ScopeGrants access to
line_items:readGET /api/v1/public/line-items
rollups:readGET /api/v1/public/rollups/daily
anomalies:readGET /api/v1/public/anomalies
usage:writePOST /api/v1/public/ingest/otel (OpenTelemetry) and /api/v1/public/ingest/focus (custom-provider FOCUS)

GET /api/v1/public/line-items

Scope: line_items:read

Returns cursor-paginated raw cost line items for the requested date range. Each record corresponds to one billed line item from a connected provider.

Query parameters

ParameterRequiredDescription
start_dateYesStart of date range (YYYY-MM-DD, inclusive)
end_dateYesEnd of date range (YYYY-MM-DD, inclusive)
limitNoRecords per page. Integer 1–1000, default 50.
cursorNoOpaque cursor from meta.next_cursor of the previous page.

Example response

{
  "data": [
    {
      "id": "li_01hxyz",
      "usage_date": "2025-01-15",
      "provider_type": "openai",
      "provider_service": "gpt-4o",
      "net_amount_usd": 12.48,
      "currency_original": "USD",
      "project_id": "proj_abc123",
      "user_email": "alice@example.com"
    }
  ],
  "error": null,
  "meta": {
    "next_cursor": "eyJpZCI6ImxpXzAxaHh5eiJ9",
    "has_more": true
  }
}

Pagination

When meta.has_more is true, pass meta.next_cursor as the cursor query parameter of your next request. Repeat until has_more is false.

GET /api/v1/public/rollups/daily

Scope: rollups:read

Returns pre-aggregated daily cost totals. Faster than fetching raw line items when you only need summary-level data for charts or dashboards.

Query parameters

ParameterRequiredDescription
start_dateYesStart of date range (YYYY-MM-DD, inclusive)
end_dateYesEnd of date range (YYYY-MM-DD, inclusive)
group_byYesDimension to group by: provider, service, or category

Example request

curl -H "X-API-Key: your_key" \
  "https://api.stackspend.app/api/v1/public/rollups/daily?start_date=2025-01-01&end_date=2025-01-31&group_by=provider"

Example response

{
  "data": [
    {
      "date": "2025-01-15",
      "cost_usd": 248.30,
      "provider_type": "aws",
      "service": null,
      "category": null
    },
    {
      "date": "2025-01-15",
      "cost_usd": 87.12,
      "provider_type": "openai",
      "service": null,
      "category": null
    }
  ],
  "error": null,
  "meta": {}
}

GET /api/v1/public/anomalies

Scope: anomalies:read

Returns anomaly alerts detected by StackSpend's ML models. Use this endpoint to sync alerts into your own incident management or on-call tooling.

Query parameters

ParameterRequiredDescription
limitNoMaximum records to return (default 10, max 100)
sinceNoISO 8601 timestamp — only return anomalies detected after this time
provider_typeNoFilter by provider slug (e.g. openai, aws)
severityNolow | medium | high | critical
statusNonew | acknowledged | resolved | false_positive | dismissed

Example response

{
  "data": [
    {
      "id": "anom_09abc",
      "provider_type": "openai",
      "service": "gpt-4o",
      "detected_date": "2025-01-16T08:23:00Z",
      "actual_cost": 340.12,
      "expected_cost": 95.00,
      "deviation_percent": 258.0,
      "severity": "critical",
      "alert_type": "spend_spike",
      "status": "new"
    }
  ],
  "error": null,
  "meta": {}
}

POST /api/v1/public/ingest/focus/:connectionId

Scope: usage:write

Submit cost data for a custom provider using the StackSpend FOCUS Profile v1 — our versioned format on top of FOCUS 1.2. Create the custom provider in Settings → Integrations to get its connectionId and an ingest key. The same rows can be loaded in the UI via CSV column-mapping import.

Request body

{ format: "stackspend.focus", version: "1", rows: [...] }. Required columns per row: ChargePeriodStart, BilledCost, ServiceName, ChargeCategory (one of Usage, Purchase, Tax, Credit, Adjustment). Recommended: EffectiveCost (the net / amortized figure used by analytics; defaults to BilledCost), ChargePeriodEnd, BillingCurrency. Any other FOCUS column (RegionId, ResourceId, BillingAccountId, SubAccountId, ConsumedQuantity/Unit, Tags, …) is mapped or preserved.

Example request

curl -X POST \
  "https://api.stackspend.app/api/v1/public/ingest/focus/CONNECTION_ID" \
  -H "Authorization: Bearer ss_live_..." \
  -H "Content-Type: application/json" \
  -d '{
    "format": "stackspend.focus",
    "version": "1",
    "rows": [
      {
        "ChargePeriodStart": "2026-06-01",
        "ChargePeriodEnd": "2026-06-02",
        "BilledCost": 12.50,
        "EffectiveCost": 12.50,
        "BillingCurrency": "USD",
        "ChargeCategory": "Usage",
        "ServiceName": "Compute"
      }
    ]
  }'

Response (202 Accepted)

{
  "data": {
    "accepted": true,
    "batch_id": "…",
    "row_count": 1,
    "cost_line_item_count": 1
  },
  "error": null
}

Error codes

All errors use the same envelope shape. The HTTP status code and error.code field tell you what went wrong:

{
  "data": null,
  "error": {
    "code": "RATE_LIMITED",
    "message": "You have exceeded 60 requests per minute. Retry after 30 seconds."
  },
  "meta": {}
}
CodeHTTP statusMeaning
UNAUTHORIZED401No API key provided, or the key is malformed.
FORBIDDEN403Key is valid but has been revoked or deleted.
INVALID_SCOPE403Key does not have the scope required by this endpoint.
PLAN_RESTRICTED403Workspace is not on a plan that includes API access.
VALIDATION_ERROR400A required parameter is missing or has an invalid value.
RATE_LIMITED429Too many requests. Back off and retry after the indicated delay.
INTERNAL_ERROR500Unexpected server error. Retry with exponential backoff; contact support if it persists.

Related

API Reference — StackSpend Docs — StackSpend Docs