# SDKs

Costfluent publishes three clients for the Public API, open source under the Apache 2.0 licence: [costfluent-go](https://github.com/costfluent/costfluent-go) for Go, [costfluent-python](https://github.com/costfluent/costfluent-python) for Python and [costfluent-typescript](https://github.com/costfluent/costfluent-typescript) for TypeScript. The Python and TypeScript clients are generated from the API's OpenAPI document and cover every operation; the Go client is written by hand, with typed methods that its tests hold to the same document. All three send your API token as a bearer token, follow list pages, retry what is safe to retry and hand amounts of money over as exact decimal strings.

## Before you start

<PlanAvailability feature="ApiTokens" plan="Pro" />

- Creating the token needs the Owner, Integration Owner or Editor organization role; see [Public API authentication](/api/authentication).
- Go 1.24 or later, Python 3.11 or later, or Node.js 20 or later. The TypeScript client runs wherever `fetch` does, which includes Deno, Bun and edge runtimes.

## Create a token

<Stepper>

1. Open **Settings**, then **API tokens**, and select **Create API token**.
2. Enter a **Name** that says which script uses the token, such as `Nightly budget export`.
3. Under **Access**, choose the workspace the script works in, so the token reaches nothing else. Keep **Whole organization** only when the script reads several workspaces.
4. Under **Start from**, choose **Read only**, then select the write capabilities the script needs and clear every capability it does not call. The examples on this page need **View workspaces** and, to create a folder, **Create folders**.
5. Choose an **Expiration**, such as 90 days, and select **Create API token**.
6. Copy the token from **Your API token**. It is shown only once.

</Stepper>

:::caution
The clients do not read the token from anywhere themselves: your code passes it in. Keep it in an environment variable or a secret store, never in source code, and run the TypeScript client on a server: a token in a browser bundle is a token anyone can read.
:::

## Install a client

<CodeTabs>

```bash title="Go"
go get github.com/costfluent/costfluent-go
```

```bash title="Python"
pip install costfluent
```

```bash title="TypeScript"
npm install @costfluent/sdk
```

</CodeTabs>

The Go package is imported as `github.com/costfluent/costfluent-go/costfluent`.

## Make a first call

Each example reads the token from `COSTFLUENT_API_TOKEN` and lists every workspace the token can see.

<CodeTabs>

```go title="Go"
package main

import (
	"context"
	"fmt"
	"log"
	"os"

	"github.com/costfluent/costfluent-go/costfluent"
)

func main() {
	client := costfluent.NewClient(costfluent.WithAPIKey(os.Getenv("COSTFLUENT_API_TOKEN")))
	ctx := context.Background()

	workspaces, err := client.ListAllWorkspaces(ctx, 100)
	if err != nil {
		log.Fatal(err)
	}
	for _, workspace := range workspaces {
		fmt.Println(workspace.ID, workspace.Name)
	}
}
```

```python title="Python"
import os

from costfluent import create_client, paginate
from costfluent.api.workspaces import list_workspaces

client = create_client(os.environ["COSTFLUENT_API_TOKEN"])

for workspace in paginate(list_workspaces.sync, client=client, limit=100):
    print(workspace.id, workspace.name)
```

```typescript title="TypeScript"
import * as costfluent from "@costfluent/sdk";

const client = costfluent.createClient({ token: process.env.COSTFLUENT_API_TOKEN! });

for await (const workspace of costfluent.paginate((querySerializer) =>
  client.GET("/v1/workspaces", { params: { query: { limit: 100 } }, querySerializer }),
)) {
  console.log(workspace.id, workspace.name);
}
```

</CodeTabs>

In Python, each operation is a module under `costfluent.api`, with `sync` and `asyncio` functions and their `_detailed` twins that also return the status and headers; the models are under `costfluent.models`. In TypeScript, an operation is addressed by its method and path, with the path, query and body typed from the API. In Go, each operation the client covers is a method on it.

## Read every page

A list answers one page at a time ([Public API pagination](/api/pagination)). Each client reads them all for you:

- **Go**: a paginated resource has a `ListAll` method, such as `ListAllWorkspaces(ctx, pageSize)` or `ListAllBudgets(ctx, workspaceID, pageSize)`, which requests page after page until `links.next` is empty. The plain list method reads one page with `PageOptions` and returns its `Links`.
- **Python**: `paginate` takes a generated `sync` function and its arguments and yields every item; `apaginate` does the same for an `asyncio` function.
- **TypeScript**: `paginate` takes a function that makes the typed call and passes on the `querySerializer` it is handed, and yields every item.

Python and TypeScript follow `links.next`, and always request the next page from the client's own base URL, so the token goes nowhere else.

### Read cost data from one state

A sync that lands while you page through cost data can make a later page repeat a row or skip one ([Notice a sync between pages](/api/pagination#notice-a-sync-between-pages)). Read the first page, then send its `meta.dataVersion` with the rest: `DataVersion` on `CostDataQuery` in Go, `data_version` in Python, `dataVersion` in TypeScript. Python's and TypeScript's `paginate` keep it from page to page, because `links.next` carries it.

<CodeTabs>

```go title="Go"
query := &costfluent.CostDataQuery{
	CostFilterOptions: costfluent.CostFilterOptions{StartDate: "2026-09-01", EndDate: "2026-09-30"},
}
var rows []costfluent.CostDataPoint
for query.Page = 1; ; query.Page++ {
	page, err := client.QueryCostData(ctx, query)
	if err != nil {
		log.Fatal(err)
	}
	rows = append(rows, page.Data...)
	if !page.Links.HasNextPage() {
		break
	}
	query.DataVersion = page.Meta.DataVersion
}
```

```python title="Python"
import datetime

from costfluent import paginate
from costfluent.api.cost_data import get_cost_data

window = {"start_date": datetime.date(2026, 9, 1), "end_date": datetime.date(2026, 9, 30), "limit": 5000}

first = get_cost_data.sync(client=client, **window)
rows = [
    *first.data,
    *paginate(get_cost_data.sync, client=client, **window, page=2, data_version=first.meta.data_version),
]
```

```typescript title="TypeScript"
const query = { startDate: "2026-09-01", endDate: "2026-09-30", limit: 5000 };

const { data: first } = await client.GET("/v1/costs", { params: { query } });
const rows = [...first!.data];
for await (const row of costfluent.paginate((querySerializer) =>
  client.GET("/v1/costs", {
    params: { query: { ...query, page: 2, dataVersion: first!.meta.dataVersion } },
    querySerializer,
  }),
)) {
  rows.push(row);
}
```

</CodeTabs>

A page whose data a sync changed since the first fails with the API's `409`: `costfluent.IsConflict(err)` in Go, a `CostfluentError` whose `code` is `conflict` in Python and TypeScript. Read the window again from page 1.

## Money stays exact

An amount of money arrives as the exact decimal string the API writes, such as `"1250.50000000"`, never as a binary float ([Public API data formats](/api/formats)).

- **Go**: an amount is a `costfluent.Decimal`. `Equal` and `Cmp` compare two amounts by value, whatever their scale, `Rat` returns the exact value, and `costfluent.ParseDecimal` checks an amount before you send it.
- **Python**: an amount is a `str`. Convert it with `decimal.Decimal(amount)` to calculate with it.
- **TypeScript**: an amount is a `string`. Keep it as one, or use a decimal library to calculate with it.

## Retries

All three clients retry on their own, by the same rules:

- A `429` is retried for every method, after the `Retry-After` or `RateLimit-Reset` the API sends ([Public API rate limits](/api/rate-limits)).
- A `5xx`, a connection error, or a `404` that is not the API's own error body but a proxy's, is retried for `GET`, `HEAD`, `PUT`, `DELETE` and creates. Other `POST` requests, which start work such as a sync, are not retried, and neither is a request with `If-Match`: resending one the API had already applied could run it twice or fail on its own change.
- Retries back off exponentially from one second, with jitter, at most three times. Python's `max_retries` and TypeScript's `maxRetries` change the count.
- A wait over 60 seconds means the token's request budget is spent: the call fails at once with the `429` instead of waiting it out.
- A request that times out is not retried. The timeout is 30 seconds unless you set another: `WithTimeout` in Go, `timeout` in Python, `timeoutMs` in TypeScript.

## Creates run once

Every create sends a fresh `Idempotency-Key` and resends the same key on each of its retries, so the API answers a retried create with its first response instead of creating a second object ([Public API data formats](/api/formats)). Python and TypeScript let you choose the key, for example so that a job run again soon after a failure gets the folder the first run created; the Go client always generates its own.

<CodeTabs>

```go title="Go"
folder, err := client.CreateFolder(ctx, &costfluent.CreateFolderInput{
	WorkspaceID: "wsp_...",
	Title:       "Finance",
})
```

```python title="Python"
from costfluent import models
from costfluent.api.folders import create_folder

folder = create_folder.sync(
    client=client,
    body=models.CreateFolderRequest(workspace_id="wsp_...", title="Finance"),
    idempotency_key="finance-folder-setup",
)
```

```typescript title="TypeScript"
const { data: folder } = await client.POST("/v1/folders", {
  params: { header: { "Idempotency-Key": "finance-folder-setup" } },
  body: { workspaceId: "wsp_...", title: "Finance" },
});
```

</CodeTabs>

A key the API already saw with another request is refused with `idempotency_key_reused`: `costfluent.IsIdempotencyKeyReused(err)` in Go, `is_idempotency_key_reused` in Python and `isIdempotencyKeyReused` in TypeScript.

## Versions and the contract

The three clients share one version number, `1.MINOR.PATCH`; Go modules write it with a leading `v`. The major is the API's path version, `v1`. The minor rises with each release that carries a newer API contract, and the patch is a fix to the client alone.

Each client also carries the SHA-256 of the Public API document it was built from: `costfluent.ContractSHA256` in Go, `costfluent.CONTRACT_SHA256` in Python and `CONTRACT_SHA256`, exported by `@costfluent/sdk`, in TypeScript. Two clients with the same hash were built from the same contract. What version 1 of the API may and may not change is [Public API versioning](/api/versioning).

## How it behaves

- Once its retries are spent, a call that the API refuses fails with the API's error: a `*costfluent.APIError` in Go, a `CostfluentError` in Python and TypeScript, each with the status and the API's `code` ([Public API errors](/api/errors)).
- A proxy's `404` while the API restarts never reads as a missing object: `costfluent.IsNotFound(err)` in Go, `is_not_found` in Python and `isNotFound` in TypeScript are true only for the API's own answer.
- In Go, a workspace-scoped method takes a workspace ID, or falls back to the client's `WithWorkspace` default; a call with neither fails before it is sent. A workspace token reads its own workspace only, and the API refuses any other.
- Every call counts against the token's request limit.
- The clients call `https://api.costfluent.com` unless you set another base URL.

## Related

- [Public API authentication](/api/authentication)
- [Public API pagination](/api/pagination)
- [Public API data formats](/api/formats)
- [MCP server](/developers/mcp)
- [API reference](/api)
