API Reference
Use the StackSpend REST API to pull cost line items, pre-aggregated rollups, and anomaly alerts into your own tooling.
Overview
The StackSpend API is a REST API hosted at https://api.stackspend.com. 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
}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.com/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.com/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.
| Scope | Grants access to |
|---|---|
| line_items:read | GET /api/v1/public/line-items |
| rollups:read | GET /api/v1/public/rollups/daily |
| anomalies:read | GET /api/v1/public/anomalies |
| usage:write | POST /api/v1/otel-ingest (OpenTelemetry ingest) |
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
| Parameter | Required | Description |
|---|---|---|
| start_date | Yes | Start of date range (YYYY-MM-DD, inclusive) |
| end_date | Yes | End of date range (YYYY-MM-DD, inclusive) |
| limit | No | Records per page. Integer 1–1000, default 50. |
| cursor | No | Opaque 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
| Parameter | Required | Description |
|---|---|---|
| start_date | Yes | Start of date range (YYYY-MM-DD, inclusive) |
| end_date | Yes | End of date range (YYYY-MM-DD, inclusive) |
| group_by | Yes | Dimension to group by: provider, service, or category |
Example request
curl -H "X-API-Key: your_key" \ "https://api.stackspend.com/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
| Parameter | Required | Description |
|---|---|---|
| limit | No | Maximum records to return (default 50, max 500) |
| since | No | ISO 8601 timestamp — only return anomalies detected after this time |
| provider_type | No | Filter by provider slug (e.g. openai, aws) |
| severity | No | low | medium | high | critical |
| status | No | new | 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": {}
}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": {}
}| Code | HTTP status | Meaning |
|---|---|---|
| UNAUTHORIZED | 401 | No API key provided, or the key is malformed. |
| FORBIDDEN | 403 | Key is valid but has been revoked or deleted. |
| INVALID_SCOPE | 403 | Key does not have the scope required by this endpoint. |
| PLAN_RESTRICTED | 403 | Workspace is not on a plan that includes API access. |
| VALIDATION_ERROR | 422 | A required parameter is missing or has an invalid value. |
| RATE_LIMITED | 429 | Too many requests. Back off and retry after the indicated delay. |
| INTERNAL_ERROR | 500 | Unexpected server error. Retry with exponential backoff; contact support if it persists. |
