> ## Documentation Index
> Fetch the complete documentation index at: https://docs.costgraph.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Run the exporter

> Read your bill where it already lives and push it to CostGraph, from anywhere

The exporter is a single program you run in your own environment. It reads a
provider's bill, converts it to [FOCUS 1.2](https://focus.finops.org), and pushes
it to CostGraph. Your provider credentials stay with you; only the FOCUS rows
leave.

Use it when you would rather not hand us a credential, when the bill sits
somewhere we cannot reach, or when your provider publishes a FOCUS export you
already have. It is the same code CostGraph runs when it pulls for you, so the
data lands identically either way.

## Before you start

1. Open **Settings -> Integrations** and choose **FOCUS push**. Pick the tenant
   the charges belong to, and copy the **connection id** (`fpc_...`).
2. Open **Settings -> API keys** and create a key with the **`focus:write`**
   scope. Add **`deployment:register`** as well if you will run the container,
   which pulls the image from our registry.

One connection carries one provider. Exporting two providers means two
connections.

## Run it

<Tabs>
  <Tab title="Container">
    ```bash theme={null}
    printf '%s' "$COSTGRAPH_API_KEY" | docker login registry.costgraph.ai -u x --password-stdin

    docker run --rm \
      -e COSTGRAPH_API_KEY -e COSTGRAPH_CONNECTION_ID \
      -e OBJECTSTORE_BUCKET=my-focus-export \
      -e OBJECTSTORE_PREFIX=focus-1-2/ \
      -e OBJECTSTORE_LAYOUT=manifest \
      registry.costgraph.ai/focus-exporter:latest \
      --provider objectstore --sink costgraph
    ```

    The container runs once and exits, reading the current month. Schedule it
    with cron, a systemd timer, or your orchestrator; pass `--month YYYY-MM` to
    backfill a past one.
  </Tab>

  <Tab title="Kubernetes">
    ```bash theme={null}
    helm repo add costgraph https://charts.costgraph.ai

    helm install focus-exporter costgraph/focus-exporter \
      --set costgraph.apiKey=$COSTGRAPH_API_KEY \
      --set costgraph.connectionId=fpc_... \
      --set imagePullSecret.apiKey=$COSTGRAPH_API_KEY \
      --set config.OBJECTSTORE_BUCKET=my-focus-export \
      --set config.OBJECTSTORE_PREFIX=focus-1-2/
    ```

    A `CronJob`, daily at 06:00 by default. Settings under `config` are the
    provider's; credentials go under `credentials` and become a Secret.

    A value passed with `--set` is stored in the Helm release in plain text.
    For anything but a trial, create the Secrets yourself and leave the values
    empty - the chart looks for them by name.
  </Tab>

  <Tab title="Binary">
    ```bash theme={null}
    curl -fsSL https://setup.costgraph.ai/bin/install.sh | sh

    COSTGRAPH_API_KEY=... COSTGRAPH_CONNECTION_ID=fpc_... \
    OBJECTSTORE_BUCKET=my-focus-export \
    focus-exporter --provider objectstore --sink costgraph
    ```

    It reads the current month; add `--month YYYY-MM` to backfill. Writing the
    records to a file instead of pushing them is a good way to see what would be
    sent: drop `--sink costgraph`.
  </Tab>
</Tabs>

## Settings

A setting is read from a flag, then a config file, then the environment, so any
of the three works and the more specific one wins. Credentials are usually
environment variables; everything else reads well in a file, at `--config`,
`./focus-exporter.yaml`, or `/etc/focus-exporter/config.yaml`:

```yaml theme={null}
provider: objectstore
sink: costgraph
OBJECTSTORE_BUCKET: my-focus-export
OBJECTSTORE_PREFIX: focus-1-2/
OBJECTSTORE_LAYOUT: manifest
```

Each provider's variables are listed on its own integration page, and all of
them together in the
[provider reference](/costgraph/integrations/exporter-reference).

## Reading a bill from storage

Some providers do not answer a billing API at all; they write the bill to
storage on a schedule. AWS Data Exports writes Parquet to S3, Azure Cost
Management writes to Azure Blob Storage, and STACKIT publishes `part_000X.csv.gz`
to its own Object Storage.

The `objectstore` provider reads all of them. Point it at where the bill is:

| Where the bill is                        | Set                                                                                                      |
| ---------------------------------------- | -------------------------------------------------------------------------------------------------------- |
| AWS S3                                   | `OBJECTSTORE_BUCKET`, and nothing else if the exporter's role can read it                                |
| Azure Blob Storage                       | `OBJECTSTORE_ENDPOINT=https://<account>.blob.core.windows.net` and the container in `OBJECTSTORE_BUCKET` |
| S3-compatible (STACKIT, R2, MinIO, Ceph) | `OBJECTSTORE_ENDPOINT` and a read-only key pair                                                          |
| Files you already have                   | `OBJECTSTORE_ENDPOINT=file:///bills`                                                                     |

You do not normally set `OBJECTSTORE_KIND`: it is inferred from the endpoint
(an `.blob.core.windows.net` host is Azure, a `file://` path is a directory,
anything else is S3). Set it only to be explicit.

`OBJECTSTORE_LAYOUT` answers one question: **given all the files in the bucket,
which ones make up a billing period?** A provider does not always write one tidy
file per month.

| Layout           | Reads                                        | Use it for                                                                 |
| ---------------- | -------------------------------------------- | -------------------------------------------------------------------------- |
| `flat` (default) | every file under the prefix                  | a bucket that holds one copy of each file                                  |
| `latest-run`     | only the newest export of each period        | a provider that rewrites the whole period on every run, like STACKIT       |
| `manifest`       | exactly the files a published manifest lists | AWS Data Exports and Azure, which write a manifest naming their data files |

Prefer `manifest` where the export publishes one: nothing is guessed from file
names, and a run that lands while the exporter is reading cannot be read
half-written.

`.csv`, `.csv.gz` and `.parquet` are all read.

## What a run does

1. Works out which files or API calls cover the window you asked for.
2. Reads them, repairs the deviations from FOCUS we know about, and drops rows
   that are not FOCUS at all, naming each one it drops.
3. Pushes in pages, each carrying its position in the run, so a page that has to
   be retried replaces itself rather than duplicating.
4. On the last page CostGraph builds the window.

A run holds no state, so a failed run is re-run rather than repaired. Pushing a
month again replaces it rather than doubling it, which is what makes a daily
schedule over the running month safe.

## What it needs

* Outbound HTTPS to CostGraph and to your provider. Nothing inbound.
* Read-only credentials. The exporter never writes to your bucket or your
  provider.
* Memory in proportion to the largest file, and only for Parquet, which cannot
  be read as a stream. CSV of any size is read as it goes.
