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

# Enrichments

> Beta lookup files that fill business columns from a billing column. Publish static values or a dynamic split from a BigQuery usage table.

<Note>
  Enrichments are in beta. Each one is a lookup file you keep in git and publish through the API. The Dimensions page reviews and publishes them. It does not edit the files.
</Note>

An **enrichment** fills business columns from a column already on the bill. One file reads one column, such as `cos_namespace`, and writes one or more columns, such as `owner_org`. A file can hold thousands of keys. After you publish, Costory applies the files on the next billing update, including history, and the output columns are available in [Explorer](/features/cost-explorer), dashboards, alerts, and reports.

Use an enrichment when the allocation is a table you want in git. Use a [virtual dimension](/features/tagging/dimensions#create-a-virtual-dimension-from-rules) when someone should read and change the rules in the app.

| | Virtual dimension | Enrichment (beta) |
| - | - | - |
| What it is | A short list of rules | A lookup file |
| Scale | Tens of rules | Thousands of keys |
| Where it lives | Dimensions, or the assistant | Git, then the API |
| Who changes it | Anyone on the page | An organization admin |

The Enrichments section on **Dimensions** appears once the organization has a file. There is no **Add enrichment** action.

## Example files

One YAML file is one enrichment. The `name` in the file is the name you upload. `priority` sets the run order: a lower number runs first. `targetColumns` lists the columns the file writes.

* **`merge`** keeps a value the row already has, and fills the column only when it is empty.
* **`replace`** overwrites the column with the value from the file.

`conditions` is optional. When you set it, it is a CEL expression, and every field starts with `input.`.

Upload the document as JSON. The body below is that JSON, shown as YAML so it stays readable in git. A `GET` of the file is a valid upload body.

### Static lookup

A static file writes fixed values. This one maps a Kubernetes namespace to an organization. Namespaces that already carry `owner_org` keep it, because the columns are `merge`.

```yaml theme={null}
name: namespace_to_org
description: Namespaces whose pod label does not resolve, mapped to the responsible organization
keyField: cos_namespace
priority: 2
conditions: 'input.cos_cluster_name != null && input.cos_namespace != ""'
targetColumns:
  - name: owner_team
    mode: merge
  - name: engineering_managerr
    mode: merge
entries:
  - key: payments
    then:
      owner_team: Commerce
      engineering_managerr: Thomas
  - key: fluent-bit
    then:
      owner_team: Platform
      engineering_managerr: Mary
```

A row with `cos_namespace = payments` and an empty `owner_org` becomes `owner_org = Commerce`, `owner_org_code = COM`. A namespace with no entry is left unchanged.

### Static allocation

A static allocation splits one key across several values. You write the shares in the file. The weights of one entry sum to 1.

```yaml theme={null}
name: 03_account_split
description: Split a shared account across the teams that use it
keyField: cos_account_id
priority: 3
targetColumns:
  - name: owner_org
    mode: replace
entries:
  - key: "111122223333"
    then:
      - owner_org: Platform
        weight: 0.6
      - owner_org: Data
        weight: 0.4
```

Cost on account `111122223333` is reported as 60% Platform and 40% Data. The shares stay as written until you change the file and publish again.

### Dynamic allocation

A dynamic allocation splits one key from a BigQuery table, per day, instead of from weights in the file. The table is `project.dataset.table`. `dateColumn` is the day, `metricColumn` is the measure, and `propagates` lists the target columns the table fills. `integrationId` is an active BigQuery integration of this organization.

This file splits the shared ML platform by CPU hours. Each row of the metric table carries `app`, `team`, and `cost_center`, and that day's cost follows `cpu_hours`.

```yaml theme={null}
name: 05_shared_ml_platform
description: Split a shared platform by daily CPU hours
keyField: cos_feature
priority: 5
targetColumns:
  - name: app
    mode: replace
  - name: team
    mode: replace
  - name: cost_center
    mode: replace
entries:
  - key: shared-ml-platform
    then:
      integrationId: clx8f2k9a0001qw3h7bn2m5vz
      metric:
        table: costory-prod.example_metrics.shared_ml_platform_usage
        dateColumn: date
        metricColumn: cpu_hours
      propagates:
        - app
        - team
        - cost_center
```

Replace `integrationId` and `table` with your integration and your metric table. `propagates` can list a subset of `targetColumns`. A column you leave out stays unset for that entry.

The same split, edited as rules in the app, is [shared cost allocation](/features/tagging/shared-cost-allocation). Use that when the rule belongs on the Dimensions page. Use this file when the split belongs in git with the rest of the enrichments.

## Upload, test, and publish

Writes require an organization admin and a bearer token from the Costory app. See [API overview](/api-reference/overview) for the token, and the [Enrichments API](/api-reference/enrichments/overview) for each call. To keep the files in git and publish them with Terraform, use [Publish with Terraform](#publish-with-terraform).

<Steps>
  <Step title="Upload each file as a draft">
    `PUT /enrichments/{name}` stores that file as a draft. The `{name}` in the path matches `name` in the file. Uploading the live file again clears its draft.

    `DELETE /enrichments/{name}` drafts the removal of a live file.
  </Step>

  <Step title="Test one row">
    Open **Dimensions**. In **Enrichments**, click **Test enrichments**, fill the columns for one billed row, and click **Test**.

    Costory runs that row through the live files with the drafts applied, and shows each file's hit or miss and the resulting values. A dynamic allocation names the metric table. It does not compute the shares. Those appear after publish, on the next billing update.
  </Step>

  <Step title="Publish">
    Click **Publish** on the Dimensions page and include the enrichment drafts, or call `POST /enrichments/draft/publish`.

    Publish stores every draft in one step and starts a billing update. A file set that does not fit together is refused, and the live files stay as they are.
  </Step>
</Steps>

To roll back, restore the previous files in git, upload them, and publish again.

<Tip>
  Test the row you care about before publishing. The test uses the values you type. It does not scan the bill.
</Tip>

## Publish with Terraform

Keep one YAML file per enrichment in an `enrichments/` directory next to your Terraform configuration. The [Costory provider](https://registry.terraform.io/providers/costory-io/costory/latest) (0.3.1 or later) reads each file with [`costory_enrichment`](https://registry.terraform.io/providers/costory-io/costory/latest/docs/resources/enrichment) and stores it as a draft. [`costory_enrichments_publish`](https://registry.terraform.io/providers/costory-io/costory/latest/docs/resources/enrichments_publish) then compiles that set and makes it live in the same apply.

The file basename must match the `name` field inside the file. `namespace.yaml` requires `name: namespace`. Both `.yml` and `.yaml` are included.

```hcl theme={null}
terraform {
  required_providers {
    costory = {
      source  = "costory-io/costory"
      version = ">= 0.3.1"
    }
  }
}

variable "costory_api_token" {
  type        = string
  description = "Costory API token. An organization admin key is required to write enrichment drafts."
  sensitive   = true
}

provider "costory" {
  token = var.costory_api_token
}

locals {
  yml_files  = fileset("${path.module}/enrichments", "*.yml")
  yaml_files = fileset("${path.module}/enrichments", "*.yaml")

  # Basename without extension. It must match the name field inside the file.
  enrichment_files = {
    for filename in setunion(local.yml_files, local.yaml_files) :
    trimsuffix(trimsuffix(filename, ".yaml"), ".yml") => filename
  }
}

resource "costory_enrichment" "all" {
  for_each = local.enrichment_files

  name = each.key
  file = "${path.module}/enrichments/${each.value}"
}

# Compiles every draft and makes that set live in one transaction.
# A name that leaves the map is removed in the same apply.
resource "costory_enrichments_publish" "all" {
  enrichments = {
    for enrichment in costory_enrichment.all :
    enrichment.name => enrichment.content_sha256
  }
}
```

Set the token in `terraform.tfvars` or as `TF_VAR_costory_api_token`, then run `terraform init` and `terraform apply`.

`terraform apply` writes the drafts and publishes them. Costory compiles the live set with every draft applied and starts a billing update. A set that fails to compile is refused, and the live files stay as they are.

Removing a file and applying again drops that enrichment in the same publish. Destroying an enrichment resource drafts its removal. The live enrichment stays until publish runs.

Test a row on **Dimensions** before you rely on the result. In **Enrichments**, click **Test enrichments**, fill the columns for one billed row, and click **Test**. The test runs the live files with the drafts from the last apply.

## Next steps

<CardGroup cols={2}>
  <Card title="Dimensions" icon="layer-group" href="/features/tagging/dimensions">
    Merge tags and write rules in the app
  </Card>

  <Card title="Shared Cost Allocation" icon="chart-pie" href="/features/tagging/shared-cost-allocation">
    Split a shared cost from a usage metric, as a rule
  </Card>

  <Card title="API overview" icon="code" href="/api-reference/overview">
    Authenticate an admin token for uploads
  </Card>

  <Card title="Enrichments API" icon="code" href="/api-reference/enrichments/overview">
    Upload a draft and publish the set
  </Card>

  <Card title="Explorer" icon="magnifying-glass" href="/features/cost-explorer">
    Group costs by the columns an enrichment writes
  </Card>
</CardGroup>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.