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

# GET /v1/config/:projectId

> Fetch the pre-built config bundle for a project. Supports ETag caching for efficient polling.

Returns the config bundle for a project. The bundle contains all parameters, layers, policies, and allocations needed for local SDK resolution.

## Request

```
GET /v1/config/:projectId
```

### Path parameters

| Parameter   | Type     | Description               |
| ----------- | -------- | ------------------------- |
| `projectId` | `string` | Your Traffical project ID |

### Headers

| Header          | Required | Description                                                                             |
| --------------- | -------- | --------------------------------------------------------------------------------------- |
| `Authorization` | Yes      | `Bearer traffical_sk_...` (requires `sdk:read` scope)                                   |
| `If-None-Match` | No       | ETag from a previous response. Returns `304 Not Modified` if the bundle hasn't changed. |

### Query parameters

| Parameter | Type     | Default      | Description      |
| --------- | -------- | ------------ | ---------------- |
| `env`     | `string` | `production` | Environment name |

## Response

### 200 OK

Returns the config bundle as JSON.

**Headers:**

| Header          | Description                                                                         |
| --------------- | ----------------------------------------------------------------------------------- |
| `ETag`          | Version identifier for the bundle. Use with `If-None-Match` on subsequent requests. |
| `Cache-Control` | `public, max-age=60, must-revalidate`                                               |

**Body:**

```json theme={null}
{
  "version": "2026-05-21T09:58:11Z",
  "orgId": "org_acme",
  "projectId": "proj_marketplace",
  "env": "production",
  "hashing": {
    "unitKey": "userId",
    "bucketCount": 1000,
    "algorithm": "sha256-v2"
  },
  "parameters": [
    {
      "key": "checkout.button.color",
      "type": "string",
      "default": "#1E6EFB",
      "layerId": "layer_checkout",
      "namespace": "checkout"
    },
    {
      "key": "checkout.show_trust_badges",
      "type": "boolean",
      "default": false,
      "layerId": "layer_checkout",
      "namespace": "checkout"
    }
  ],
  "layers": [
    {
      "id": "layer_checkout",
      "policies": [
        {
          "id": "policy_color_test",
          "key": "color_test",
          "state": "running",
          "kind": "static",
          "allocations": [
            {
              "id": "alloc_a",
              "name": "control",
              "key": "control",
              "bucketRange": [0, 499],
              "overrides": { "checkout.button.color": "#1E6EFB" }
            },
            {
              "id": "alloc_b",
              "name": "treatment",
              "key": "treatment",
              "bucketRange": [500, 999],
              "overrides": { "checkout.button.color": "#22C55E" }
            }
          ],
          "conditions": []
        }
      ]
    }
  ],
  "eventDefinitions": [
    { "name": "purchase", "valueType": "currency", "unit": "USD" }
  ]
}
```

Key fields:

| Field                 | Description                                                                                                                              |
| --------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- |
| `version`             | ISO timestamp of the bundle build. Also the basis of the `ETag`.                                                                         |
| `hashing.unitKey`     | Context field used as the bucketing unit (for example, `userId`).                                                                        |
| `hashing.bucketCount` | Number of buckets per layer. Defaults to `1000`; allocation `bucketRange` values are `[0, bucketCount − 1]` inclusive.                   |
| `hashing.algorithm`   | Assignment-hash version identifier. Currently always `"sha256-v2"`; SDKs assume `"sha256-v2"` when the field is absent in older bundles. |
| `parameters`          | All parameters with their type, default (environment overrides applied), owning `layerId`, and `namespace`.                              |
| `layers`              | Layers with their **running** policies. Draft, paused, and completed policies are not included.                                          |
| `eventDefinitions`    | Event names and value types defined for the project. Only present when the project defines events.                                       |

### 304 Not Modified

Returned when the `If-None-Match` header matches the current bundle version. No body is returned.

### 401 Unauthorized

```json theme={null}
{
  "error": {
    "code": "UNAUTHORIZED",
    "message": "Invalid API key"
  }
}
```

### 403 Forbidden

Returned when the key is valid but lacks the `sdk:read` scope:

```json theme={null}
{
  "error": {
    "code": "FORBIDDEN",
    "message": "SDK read scope required"
  }
}
```

### 404 Not found

Returned when no bundle exists for the project and environment:

```json theme={null}
{
  "error": {
    "code": "NOT_FOUND",
    "message": "Bundle not found for proj_marketplace/production"
  }
}
```

### 503 Service unavailable

Returned when the config store is temporarily unavailable and no cached copy can be served. The response includes a `Retry-After` header (currently `5` seconds); keep your cached bundle or defaults and retry.

```json theme={null}
{
  "error": {
    "code": "SERVICE_UNAVAILABLE",
    "message": "Config store temporarily unavailable, retry shortly"
  }
}
```

When the store is briefly unavailable but a recent copy exists, the endpoint keeps serving that copy instead: the response is a normal `200` with an `X-Traffical-Bundle-Stale: true` header and a shorter cache TTL, so clients revalidate aggressively.

## ETag caching

The SDKs use ETag caching to poll for bundle updates efficiently:

1. First request — No `If-None-Match` header. The response contains the full bundle with an `ETag` header.
2. Subsequent requests — Include `If-None-Match: <previous-etag>`. If the bundle hasn't changed, the response is `304 Not Modified` with no body.
3. When the bundle changes — The ETag changes, and the response contains the full updated bundle.

This minimizes bandwidth when the bundle hasn't changed, which is the common case.
