Stack SpendDocs

Custom provider (FOCUS)

Track spend from any tool StackSpend doesn't natively support by pushing cost data in the FOCUS standard, via the API or a CSV import.

Custom provider logo

Custom provider (FOCUS)

Any source · push (API or CSV) · 5 min setup

What is a custom provider?

A custom provider lets you bring spend into StackSpend from any tool that isn't in our built-in list — an internal platform, a niche SaaS, or a vendor we don't yet have a native connector for. Instead of StackSpend pulling the data, you push it: either by POSTing cost rows to our API, or by uploading a CSV in the app and mapping your columns. Once it's in, custom-provider spend flows through the same dashboards, anomaly detection, forecasts and savings as every other provider.

Why it exists

Most teams have long-tail cost sources that never make it into a cost tool, leaving dashboards and budgets incomplete. Custom providers close that gap. The data format is the StackSpend FOCUS Profile — our versioned profile of the FinOps FOCUS 1.2 standard. Because it uses standard FOCUS column names, a FOCUS export from another tool (for example a Vantage custom-provider export) maps straight in.

Push-only.Custom providers are never synced on a schedule — StackSpend only holds the data you send. Send a new batch whenever your costs update (e.g. a daily job, or a CSV upload each month).
Which plan you need.CSV import works on any plan. Pushing rows to the ingest API uses the public API, which is available on the Business plan and above — requests from other plans return 403 PLAN_RESTRICTED. Generating a replacement ingest key also requires the Business plan, so if you are on another plan, store the key shown at creation somewhere safe and use CSV import.

Set up a custom provider

1

Create the provider

In Settings → Integrations, choose Custom provider (or “Can't find your provider? Add a custom one”). Give it a display name and a default currency, then Create & get ingest key.
2

Copy your ingest key

The ingest key is shown once on creation — copy it now. The provider's API key & ingest URL tab shows your full ingest URL with the connection id baked in, and (on the Business plan) lets you generate a replacement key. The key is scoped to this one provider: using it against a different connection returns 403 FORBIDDEN.
3

Send your data

Either POST FOCUS rows to the API (see below), or open the provider's Import data tab, upload a CSV, and map your columns to FOCUS fields. We auto-match columns that already use FOCUS names and save your mapping for next time.
4

Verify

Open the provider's History tab to see each ingest batch (rows, cost line items created, status). The spend then appears in your dashboards and Data Explorer.

The FOCUS Profile format

Each row is one charge. Send fully-priced rows — StackSpend stores the cost you provide (we don't price raw usage). Body shape: { "format": "stackspend.focus", "version": "1", "rows": [ … ] }. Both format and version are optional; when you send a version it must be "1", "1.1" or "1.2". Every column below works under any of them — the version records which revision you wrote against, it does not gate anything.

ColumnRequiredNotes
ChargePeriodStartYesISO 8601 or YYYY-MM-DD.
BilledCostYesGross charge. Negative for credits/refunds.
ServiceNameYesWhat the charge is for.
ChargeCategoryYesUsage, Purchase, Tax, Credit, or Adjustment. Case-insensitive. Discount, Fee and Refund are also accepted and folded into the canonical set.
EffectiveCostRecommendedNet/amortized cost used by analytics. Defaults to BilledCost.
ChargePeriodEndRecommendedExclusive end of the period.
BillingCurrencyRecommendedISO 4217. Defaults to the provider's default currency.
RegionId, ResourceId, BillingAccountId, SubAccountId, ConsumedQuantity/Unit, Tags, …OptionalStandard FOCUS dimensions; mapped or preserved on the line item.
ServiceCategoryOptionalA grouping for the service. Custom providers have no predefined service-type list, so this is kept on the line item for filtering rather than resolved to a built-in category. To group spend for reporting, prefer Tags.
x_stackspend_modelOptionalAI model behind the charge (e.g. gpt-5-mini). Surfaces the row in the AI Explorer; names are standardized automatically.
x_stackspend_tokens_input / _output / _cache_read / _cache_creationOptionalToken counts per direction (StackSpend FOCUS Profile v1.1). When present, the row is counted in the Tokens measure and powers the input/output drill-down.
x_stackspend_line_item_idOptionalYour own id for the row (StackSpend FOCUS Profile v1.2). When present it becomes the deduplication key, so you decide which rows are the same charge. Use it when two charges are identical on every other column.

Example request

curl -X POST "https://ingest.stackspend.app/api/v1/public/ingest/focus/YOUR_CONNECTION_ID" \
  -H "Authorization: Bearer YOUR_INGEST_KEY" \
  -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",
        "RegionId": "us-east-1",
        "Tags": { "env": "prod" }
      }
    ]
  }'
Explore the API.See the API reference for the full endpoint, scopes and schemas, or open the interactive API explorer to try it with your key.

Re-sending data is safe

Sending the same charge twice does not double-count it. StackSpend derives a stable identity for each row from every column that describes the charge — its charge period, service, resource and SKU, charge category, account and sub-account, region, model, tags, currency and costs — so re-submitting an identical row updates the existing line item instead of creating a second one. Costs are compared by value, so 12.5, "12.50" and "$12.50" are treated as the same charge, and tag order never matters. This makes retries and overlapping date windows safe: a daily job can re-send the trailing week every night without creating duplicates.

Two rows are only merged when every one of those columns matches. Rows that differ by team, region, model or any other dimension stay separate, so a cost-allocation export keeps one line item per team. If you have two charges that are genuinely identical on every column but should stay separate — the same service, day and price billed twice — give each one your own x_stackspend_line_item_id. When that column is present it becomes the identity, so you control exactly what counts as the same charge.

If a charge is restated at a different cost, it is a new line item rather than an update — send a matching negative-cost Adjustment or Credit row if you need the original backed out.

Limits

  • 10,000 rows per request — split larger exports into multiple batches.
  • ~5 MB request body. Large batches of wide rows can hit this before the row limit; if you receive a 413, send fewer rows per request.
  • 60 requests per minute per API key, after which requests return 429 RATE_LIMITED.
  • CSV import: the same 10,000-row ceiling, and the uploader accepts files up to 4 MB. Split a larger export and upload the parts — your saved mapping is reused, so each part is two clicks.

Errors you might see

Status & codeWhat it means
400 VALIDATION_ERRORA row is missing a required column or has a value we can't read. The message names the row and column, e.g. rows.42.BilledCost. Costs must be numbers or numeric strings — N/A and - are not accepted; omit the row instead.
400 TOO_MANY_ROWSA CSV import above 10,000 rows. Split the file and upload the parts.
401 UNAUTHORIZEDThe ingest key is wrong or has been revoked. Generating a new key revokes the previous one, and removing a provider revokes its key.
403 FORBIDDENThe key belongs to a different provider. Check the connection id in your URL matches the provider the key was issued for.
403 PLAN_RESTRICTEDThe ingest API needs the Business plan. CSV import works on any plan.
404 NOT_FOUNDNo custom provider with that connection id in your organization.
409 CONNECTION_INACTIVEThe provider has been removed. Restore it from the providers list, then generate a new ingest key — removing a provider revokes its key, so restoring alone leaves your old key returning 401.
413 PAYLOAD_TOO_LARGEThe request body is over 5 MB. Send fewer rows per request — wide rows hit this before the 10,000-row limit.
429 RATE_LIMITEDOver 60 requests a minute for this key. Retry after the interval in the Retry-After header.
500 INGEST_FAILEDSomething went wrong on our side after the batch was accepted. The batch appears as failed in the History tab with the reason; re-sending the same rows is safe.
Partial batches.A row we can't map no longer fails the whole batch. The response reports skipped and an errors list (first 50) naming each one, and every other row lands.

Frequently asked questions

How does StackSpend get the data for a custom provider?

You push it. Unlike native connectors, a custom provider is never pulled or synced on a schedule — StackSpend only holds the data you send, either by POSTing FOCUS rows to the ingest API or by uploading a CSV in the app. Send a new batch whenever your costs update, such as a daily job or a monthly CSV upload.

What credential do I need?

You need the ingest key generated when you create the provider. It is shown once on creation, and it is scoped to that one provider connection — using it against a different connection returns 403 FORBIDDEN. The provider's API key & ingest URL tab shows your full ingest URL with the connection id baked in, and lets you generate a replacement key on the Business plan.

Do I need a paid plan to use a custom provider?

CSV import works on any StackSpend plan. Pushing rows to the ingest API uses the public API, which requires the Business plan — requests from other plans return a 403 PLAN_RESTRICTED error. Generating a replacement ingest key also requires the Business plan, so on other plans keep the key shown at creation and use CSV import.

What happens if I send the same rows twice?

Nothing is double-counted. StackSpend derives a stable identity for each row from every column that describes the charge — charge period, service, resource and SKU, charge category, account and sub-account, region, model, tags, currency and costs — so re-submitting an identical row updates the existing cost line item instead of creating a second one. Rows that differ in any of those columns stay separate, so a per-team or per-model breakdown keeps one line item each. Costs are compared by value, so 12.5, "12.50" and "$12.50" count as the same charge. Retries and overlapping date windows are therefore safe.

What data format does a custom provider use?

The StackSpend FOCUS Profile, a versioned profile of the FinOps FOCUS 1.2 standard. Because it uses standard FOCUS column names, a FOCUS export from another tool maps straight in. Each row is one fully-priced charge — StackSpend stores the cost you provide and does not price raw usage.

How do I confirm my data landed?

Open the provider's History tab to see each ingest batch, including rows received, cost line items created, and status. Once ingested, custom-provider spend flows through the same dashboards, anomaly detection, forecasts, and savings as every other provider.

StackSpend Docs