Billing-sources endpoints
GET /api/v1/orgs/{org_id}/billing-sources
Section titled “GET /api/v1/orgs/{org_id}/billing-sources”List billing sources
Lists the organization’s billing sources, ordered by name.
The list is not paginated, because an organization has only a few sources. No response
includes an admin key; key_set says whether one is stored.
| Parameter | In | Type | Required | Notes |
|---|---|---|---|---|
org_id |
path | string (uuid) | yes |
| Status | Description | Body |
|---|---|---|
| 200 | Successful Response | application/json: array of BillingSourceOut |
| 422 | Validation Error | application/json: HTTPValidationError |
POST /api/v1/orgs/{org_id}/billing-sources
Section titled “POST /api/v1/orgs/{org_id}/billing-sources”Add a billing source
Adds a billing source and answers 201 with it.
For a source that fetches from a provider, the admin key is encrypted with CREDENTIALS_KEYS
and checked once with the provider (one request, 10 seconds at most). The source is stored
whatever the outcome of the check, which sets status and last_error. The key is never
returned, logged or audited.
Errors: 409 NOT_CONFIGURED when the kind fetches from a provider and CREDENTIALS_KEYS is
not set. 422 VALIDATION_ERROR on admin_key when the key is not an Admin API key of the
kind’s provider, on provider when it contradicts the kind, and on project_ids when a
project belongs to another organization. 409 BILLING_SOURCE_NAME_TAKEN when another source
of the organization has the name. 409 BILLING_SOURCE_PROJECT_TAKEN when another source of
the same provider already covers one of the projects. 429 RATE_LIMITED with Retry-After
past five key checks in the organization, then one a minute (creates and rotations count
together; a request refused before the check, a CSV source among them, spends nothing).
| Parameter | In | Type | Required | Notes |
|---|---|---|---|---|
org_id |
path | string (uuid) | yes |
Request body (application/json, required): BillingSourceCreate
| Status | Description | Body |
|---|---|---|
| 201 | Successful Response | application/json: BillingSourceOut |
| 422 | Validation Error | application/json: HTTPValidationError |
DELETE /api/v1/orgs/{org_id}/billing-sources/{source_id}
Section titled “DELETE /api/v1/orgs/{org_id}/billing-sources/{source_id}”Delete a billing source
Deletes the source with its project mappings, fetch runs and provider cost lines.
This cannot be undone, and the source’s reconciliation report stops working.
| Parameter | In | Type | Required | Notes |
|---|---|---|---|---|
org_id |
path | string (uuid) | yes | |
source_id |
path | string (uuid) | yes |
| Status | Description | Body |
|---|---|---|
| 204 | Successful Response | none |
| 422 | Validation Error | application/json: HTTPValidationError |
GET /api/v1/orgs/{org_id}/billing-sources/{source_id}
Section titled “GET /api/v1/orgs/{org_id}/billing-sources/{source_id}”Get a billing source
Returns one billing source. The admin key is never included.
| Parameter | In | Type | Required | Notes |
|---|---|---|---|---|
org_id |
path | string (uuid) | yes | |
source_id |
path | string (uuid) | yes |
| Status | Description | Body |
|---|---|---|
| 200 | Successful Response | application/json: BillingSourceOut |
| 422 | Validation Error | application/json: HTTPValidationError |
PATCH /api/v1/orgs/{org_id}/billing-sources/{source_id}
Section titled “PATCH /api/v1/orgs/{org_id}/billing-sources/{source_id}”Edit a billing source
Changes the fields sent: name, provider_scope, enabled and project_ids.
project_ids replaces the whole mapping, and provider_scope: null clears the scope. A
source that is turned off is not fetched and cannot be refreshed, and its report stays
readable. The name and project errors of POST apply here too.
| Parameter | In | Type | Required | Notes |
|---|---|---|---|---|
org_id |
path | string (uuid) | yes | |
source_id |
path | string (uuid) | yes |
Request body (application/json, required): BillingSourceUpdate
| Status | Description | Body |
|---|---|---|
| 200 | Successful Response | application/json: BillingSourceOut |
| 422 | Validation Error | application/json: HTTPValidationError |
GET /api/v1/orgs/{org_id}/billing-sources/{source_id}/reconciliation
Section titled “GET /api/v1/orgs/{org_id}/billing-sources/{source_id}/reconciliation”Reconcile a billing source with Spanlight’s figures
Compares the provider’s bill with Spanlight’s figures, per UTC day and model group.
Spanlight’s side covers the projects mapped to the source. from and to are widened to
whole UTC days; the window is at most 31 days and ends today at the latest, and without them
the report covers the last 14 whole days. Each day and group shows the provider’s amount,
Spanlight’s amount, the explained lines and the signed residual (provider minus Spanlight
minus explained). Credits and charges Spanlight cannot see are listed apart and never enter
the residual. A day is provisional until 48 hours after it ends and until a successful fetch
or upload that covers it finishes after that point.
Errors: 422 VALIDATION_ERROR on to when the window is longer than 31 days or ends after
today (UTC), including a from later than the start of today with no to, and on from
when from is not earlier than to.
| Parameter | In | Type | Required | Notes |
|---|---|---|---|---|
org_id |
path | string (uuid) | yes | |
source_id |
path | string (uuid) | yes | |
from |
query | string (date-time) or null | no | |
to |
query | string (date-time) or null | no |
| Status | Description | Body |
|---|---|---|
| 200 | Successful Response | application/json: ReconciliationOut |
| 422 | The window is invalid (VALIDATION_ERROR). |
none |
GET /api/v1/orgs/{org_id}/billing-sources/{source_id}/reconciliation.csv
Section titled “GET /api/v1/orgs/{org_id}/billing-sources/{source_id}/reconciliation.csv”Download a billing source’s reconciliation as CSV
Returns the same report as GET …/reconciliation as a CSV file.
The file has one row per day and model group. The window rules and errors are those of the JSON report.
| Parameter | In | Type | Required | Notes |
|---|---|---|---|---|
org_id |
path | string (uuid) | yes | |
source_id |
path | string (uuid) | yes | |
from |
query | string (date-time) or null | no | |
to |
query | string (date-time) or null | no |
| Status | Description | Body |
|---|---|---|
| 200 | The report, one row per group. | text/csv: any |
| 422 | The window is invalid (VALIDATION_ERROR). |
none |
POST /api/v1/orgs/{org_id}/billing-sources/{source_id}/refresh
Section titled “POST /api/v1/orgs/{org_id}/billing-sources/{source_id}/refresh”Fetch a billing source now
Queues a fetch of the provider’s cost report and answers 202 with the new run’s id.
The fetch covers the last seven whole UTC days, or the last 31 while the source has never
been fetched successfully. Follow the run with GET …/runs.
Errors: 422 VALIDATION_ERROR on kind for a CSV source. 409 BILLING_SOURCE_DISABLED when
the source is turned off. 409 NOT_CONFIGURED when BILLING_SOURCES_ENABLED is off or
CREDENTIALS_KEYS is not set. 429 RATE_LIMITED with Retry-After past the limit of one
refresh per source every 5 minutes, with a burst of 2. 409 BILLING_FETCH_IN_PROGRESS
while another run of the source is in progress; this refusal still counts towards the limit.
| Parameter | In | Type | Required | Notes |
|---|---|---|---|---|
org_id |
path | string (uuid) | yes | |
source_id |
path | string (uuid) | yes |
| Status | Description | Body |
|---|---|---|
| 202 | Successful Response | application/json: RefreshAccepted |
| 422 | Validation Error | application/json: HTTPValidationError |
POST /api/v1/orgs/{org_id}/billing-sources/{source_id}/rotate
Section titled “POST /api/v1/orgs/{org_id}/billing-sources/{source_id}/rotate”Replace a source’s admin key
Replaces the admin key of a source that fetches from a provider.
The new key is encrypted and checked once, as on create, and the check sets status and
last_error. Errors: 422 VALIDATION_ERROR on kind for a CSV source, which holds no key,
and on admin_key when the key is not an Admin API key of the source’s provider; 409 NOT_CONFIGURED when CREDENTIALS_KEYS is not set. 429 RATE_LIMITED with Retry-After
under the key-check limit of POST, which rotations share; a refused request spends nothing.
| Parameter | In | Type | Required | Notes |
|---|---|---|---|---|
org_id |
path | string (uuid) | yes | |
source_id |
path | string (uuid) | yes |
Request body (application/json, required): BillingSourceRotateIn
| Status | Description | Body |
|---|---|---|
| 200 | Successful Response | application/json: BillingSourceOut |
| 422 | Validation Error | application/json: HTTPValidationError |
GET /api/v1/orgs/{org_id}/billing-sources/{source_id}/runs
Section titled “GET /api/v1/orgs/{org_id}/billing-sources/{source_id}/runs”List a source’s runs
Lists the source’s fetches and uploads, newest first by start time.
Keyset pagination: limit from 1 to 100 (default 20), and cursor from the previous page’s
next_cursor. A failed run’s error is an HTTP status line or a short fixed phrase, never a
provider’s response body.
| Parameter | In | Type | Required | Notes |
|---|---|---|---|---|
org_id |
path | string (uuid) | yes | |
source_id |
path | string (uuid) | yes | |
limit |
query | integer | no | min 1, max 100 Default 20. |
cursor |
query | string or null | no |
| Status | Description | Body |
|---|---|---|
| 200 | Successful Response | application/json: Page_BillingRunOut_ |
| 422 | Validation Error | application/json: HTTPValidationError |
POST /api/v1/orgs/{org_id}/billing-sources/{source_id}/upload
Section titled “POST /api/v1/orgs/{org_id}/billing-sources/{source_id}/upload”Upload a billing source’s cost CSV
Stores the lines of a cost CSV as one run of a CSV source.
Send the file as multipart/form-data in a field named file: UTF-8, at most 5 MB and 100,000
data rows, with a header that lists every column of the CSV format. The whole file is checked
before anything is stored. The run covers every day from the file’s first day to its last,
and for those days the report reads only this file’s lines, so a file must hold every line of
each day it covers. A line already stored for its day takes the new figure. Every day must lie
in the 400 days up to today (UTC), today included. A source that is turned off still accepts
uploads.
Errors: 422 VALIDATION_ERROR on kind for a source that fetches from a provider. 415 UNSUPPORTED_MEDIA_TYPE when the body is not multipart/form-data. 413 PAYLOAD_TOO_LARGE over
5 MB. 429 RATE_LIMITED with Retry-After past six uploads of the source in an hour; a
refused file counts. 409 BILLING_FETCH_IN_PROGRESS while another run of the source is in
progress. 422 VALIDATION_ERROR with up to 20 errors[] of {field: "file", row, column, message} when the file is refused, in which case nothing is stored.
| Parameter | In | Type | Required | Notes |
|---|---|---|---|---|
org_id |
path | string (uuid) | yes | |
source_id |
path | string (uuid) | yes |
Request body (multipart/form-data, required): object
| Status | Description | Body |
|---|---|---|
| 200 | Successful Response | application/json: CsvUploadOut |
| 422 | Validation Error | application/json: HTTPValidationError |