# How to Add a Custom Provider to CodexBar: A 3-Step Integration Guide

> Integrate a custom provider into CodexBar easily. Follow our 3-step guide to define JSON, register your ID, and implement a secure CustomHTTPJSONFetcher for seamless integration.

- Repository: [Peter Steinberger/CodexBar](https://github.com/steipete/CodexBar)
- Tags: how-to-guide
- Published: 2026-07-08

---

**You add a custom provider to CodexBar by defining a JSON declaration in [`providers.json`](https://github.com/steipete/CodexBar/blob/main/providers.json), registering a unique `ProviderInstanceID` with the `ProviderDescriptorRegistry`, and implementing a `CustomHTTPJSONFetcher` that respects the security contracts for HTTPS-only requests and environment-based secrets.**

CodexBar discovers every usage provider through the **`ProviderDescriptorRegistry`**, which bootstraps a `ProviderDescriptor` for each value of the closed `UsageProvider` enum at launch and stores it in a thread-safe `Store`. To add a **custom provider** that lives outside this compile-time enum, you must extend the runtime architecture using the three-step integration path defined in the repository's design documentation, as implemented in `steipete/CodexBar`.

## How ProviderDescriptorRegistry Manages Providers

The `ProviderDescriptorRegistry` serves as the central authority for provider discovery in CodexBar. At application launch, it executes `ensureBootstrapped()` to populate the `descriptorsByID` list with static descriptors mapped from the `UsageProvider` enum (see [`Sources/CodexBarCore/Providers/ProviderDescriptor.swift`](https://github.com/steipete/CodexBar/blob/main/Sources/CodexBarCore/Providers/ProviderDescriptor.swift), lines 55-89).

Because `UsageProvider` is a closed enum, you cannot add cases at runtime. Instead, the architecture provides a **runtime seam** that allows you to register custom descriptors without modifying the enum. Custom providers appear in the UI automatically because `PreferencesProvidersPane` iterates over `ProviderDescriptorRegistry.all`, which includes both compiled and runtime-registered descriptors.

## Step-by-Step: Adding a Custom HTTP JSON Provider

The integration path involves three distinct steps: defining the provider in JSON, registering its instance ID, and wiring the fetcher implementation.

### Step 1: Define the Provider in providers.json

Create a JSON definition following the schema outlined in [`docs/custom-provider-design.md`](https://github.com/steipete/CodexBar/blob/main/docs/custom-provider-design.md) (lines 50-80). Place this block in your [`providers.json`](https://github.com/steipete/CodexBar/blob/main/providers.json) file or user configuration file read by `SettingsStore`.

The JSON defines a `ProviderInstanceID` (lowercase ASCII, 1-64 characters), the HTTP request configuration, and a declarative mapping for transforming the JSON response into a `ProviderFetchResult`.

```json
{
  "version": 2,
  "providers": [
    {
      "id": "acme-gateway",
      "kind": "custom-http-json",
      "label": "Acme Gateway",
      "enabled": true,
      "request": {
        "method": "GET",
        "url": "https://gateway.example.com/v1/quota",
        "authentication": { "type": "bearer" }
      },
      "mapping": {
        "primary": {
          "usedPercent": { "path": "quota.used_pct" },
          "resetsAt": { "path": "quota.reset_at", "dateFormat": "iso8601" },
          "windowMinutes": { "path": "quota.window_minutes" }
        },
        "cost": {
          "used": { "path": "spend.usd" },
          "currency": "USD",
          "period": "Approx. spend"
        },
        "identity": {
          "organization": { "path": "plan.name" },
          "loginMethod": { "literal": "api" }
        }
      }
    }
  ]
}

```

This JSON decodes into a `ProviderDefinition` (future type) that lives alongside the existing `ProviderConfig` struct in [`Sources/CodexBarCore/Config/CodexBarConfig.swift`](https://github.com/steipete/CodexBar/blob/main/Sources/CodexBarCore/Config/CodexBarConfig.swift) (lines 109-132).

### Step 2: Register the Instance with ProviderDescriptorRegistry

Extend `ProviderDescriptorRegistry` with a static `registerCustom(_:instanceID:)` method that inserts the custom descriptor into the `store` without touching the `UsageProvider` enum. This method creates a `ProviderDescriptor` using the shape defined in [`ProviderDescriptor.swift`](https://github.com/steipete/CodexBar/blob/main/ProviderDescriptor.swift) (lines 27-38).

```swift
extension ProviderDescriptorRegistry {
    /// Register a custom-http-json provider that was loaded from the user config.
    public static func registerCustom(
        id: ProviderInstanceID,
        config: CustomHTTPJSONConfig
    ) -> ProviderDescriptor {
        // Build a descriptor that uses the custom fetch plan.
        let descriptor = ProviderDescriptor(
            id: .custom,                      // Note: UsageProvider.custom does not exist; we keep the enum untouched.
            metadata: ProviderMetadata(
                displayName: config.label,
                weeklyLabel: "",
                isPrimaryProvider: false,
                tokenCost: ProviderTokenCostConfig(supportsTokenCost: false,
                                                   noDataMessage: { "N/A" })
            ),
            branding: ProviderBranding(iconStyle: .default, color: .gray),
            tokenCost: ProviderTokenCostConfig(supportsTokenCost: false,
                                               noDataMessage: { "N/A" }),
            fetchPlan: .customHTTPJSON(config: config),   // New case added to ProviderFetchPlan
            cli: ProviderCLIConfig(name: config.id, aliases: [])
        )
        return register(descriptor)
    }
}

```

### Step 3: Implement the CustomHTTPJSONFetcher

Implement a `CustomHTTPJSONFetcher` that conforms to `ProviderFetcher` and respects the security contracts: HTTPS-only URLs, no redirects, secrets via environment variables (`CODEXBAR_CUSTOM_<INSTANCE_ID>_API_KEY`), 2xx response codes, and a 1 MiB body limit. The fetcher consumes the `mapping` paths and produces a `ProviderFetchResult`.

```swift
struct CustomHTTPJSONFetcher: ProviderFetcher {
    let config: CustomHTTPJSONConfig
    let instanceID: String

    func fetch(context: ProviderFetchContext) async throws -> ProviderFetchResult {
        // 1️⃣ Validate URL (HTTPS, no user-info, no fragment)
        let url = try URL(validating: config.request.url)

        // 2️⃣ Resolve secret from env: CODEXBAR_CUSTOM_<INSTANCE_ID>_API_KEY
        let secret = ProcessInfo.processInfo.environment["CODEXBAR_CUSTOM_\(instanceID.uppercased())_API_KEY"]

        var request = URLRequest(url: url)
        request.httpMethod = config.request.method
        if config.request.authentication.type == .bearer {
            request.setValue("Bearer \(secret ?? "")", forHTTPHeaderField: "Authorization")
        }

        // 3️⃣ Perform GET with a 15s timeout, 1MiB cap
        let session = URLSession(configuration: .ephemeral)
        let (data, response) = try await session.data(for: request, delegate: nil)

        guard let http = response as? HTTPURLResponse,
              (200...299).contains(http.statusCode) else {
            throw ProviderError.httpFailure
        }

        // 4️⃣ Decode JSON and apply the declarative mapping (paths defined in config.mapping)
        let json = try JSONSerialization.jsonObject(with: data) as? [String: Any]
        let snapshot = try CustomMappingEngine.apply(json: json, mapping: config.mapping)

        return ProviderFetchResult(snapshot: snapshot)
    }
}

```

Wire this fetcher into `ProviderFetchPlan` by adding a new `customHTTPJSON` case in [`Sources/CodexBarCore/Providers/ProviderFetchPlan.swift`](https://github.com/steipete/CodexBar/blob/main/Sources/CodexBarCore/Providers/ProviderFetchPlan.swift). When `ProviderRegistry.specs(...)` builds a `ProviderFetchContext`, it will use your custom fetcher instead of the built-in `ProviderFetchPlan` used by first-party providers.

## Security and Runtime Constraints

Custom providers must adhere to strict security boundaries defined in the architecture:

- **HTTPS only**: The fetcher must reject HTTP URLs and validate TLS certificates.
- **Environment secrets**: API keys must be passed via the environment variable `CODEXBAR_CUSTOM_<INSTANCE_ID>_API_KEY`, never hardcoded in the JSON.
- **Response limits**: The fetcher must enforce a 1 MiB body limit and a 15-second timeout.
- **No redirects**: The URLSession must disable automatic redirects to prevent SSRF attacks.

## How the UI Recognizes Custom Providers

Once registered, custom providers appear automatically in the preferences UI. The `PreferencesProvidersPane` (located in [`Sources/CodexBar/PreferencesProvidersPane.swift`](https://github.com/steipete/CodexBar/blob/main/Sources/CodexBar/PreferencesProvidersPane.swift)) iterates over `ProviderDescriptorRegistry.all`, which includes both enum-based and runtime-registered descriptors. The existing menu items, widgets, and CLI commands continue to work because they address providers through the abstract `ProviderDescriptor` interface.

## Summary

- **Define** your provider in [`providers.json`](https://github.com/steipete/CodexBar/blob/main/providers.json) following the JSON schema from [`docs/custom-provider-design.md`](https://github.com/steipete/CodexBar/blob/main/docs/custom-provider-design.md), specifying the `id`, `request`, and `mapping` fields.
- **Register** the `ProviderInstanceID` by extending `ProviderDescriptorRegistry` with a `registerCustom` method that creates a `ProviderDescriptor` with a `.customHTTPJSON` fetch plan.
- **Implement** a `CustomHTTPJSONFetcher` that validates HTTPS URLs, reads secrets from environment variables, enforces response limits, and maps JSON to `ProviderFetchResult`.
- **Consume** the provider normally—the UI, CLI, and widgets treat custom providers identically to first-party providers because they use the abstract `ProviderDescriptor` interface.

## Frequently Asked Questions

### Can I add a custom provider without modifying the CodexBar source code?

Currently, no. The concrete types (`CustomHTTPJSONConfig`, `CustomHTTPJSONFetcher`, and the registration helper) are not yet implemented in the repository. Adding a custom provider requires extending the codebase as described above, though the architecture in `ProviderDescriptorRegistry` and `ProviderFetchPlan` already anticipates this extension.

### How does the CodexBar UI display custom providers?

The `PreferencesProvidersPane` automatically displays custom providers because it iterates over `ProviderDescriptorRegistry.all`, which returns every registered descriptor regardless of whether it originated from the `UsageProvider` enum or runtime registration. The UI requires no changes to support custom providers.

### What authentication methods are supported for custom HTTP JSON providers?

According to the design document, custom providers support Bearer token authentication via the `authentication.type` field in the JSON configuration. The secret must be provided through the environment variable `CODEXBAR_CUSTOM_<INSTANCE_ID>_API_KEY`, and the fetcher constructs the `Authorization: Bearer` header accordingly.

### Where is the custom provider design documented?

The complete specification lives in [`docs/custom-provider-design.md`](https://github.com/steipete/CodexBar/blob/main/docs/custom-provider-design.md) within the repository. This document defines the JSON schema, security boundaries (HTTPS-only, no redirects, 1 MiB limit), and the runtime seam for registering providers outside the `UsageProvider` enum.