# Synthetic Provider — Universal Quota Parser That Handles Any API Shape

> The Synthetic provider is designed for any OpenAI-compatible proxy; its quota API can return wildly different JSON shapes. SyntheticUsageParser does not rely on a fixed schema: it tries 10+ key aliases for each field (percentUsed/usagePercent/used_percent/percent_used/percent…), auto-detects whether a percent value is already 0-100 or 0-1, derives missing fields by algebra (used = limit − remaining), and parses duration strings like "5hr", "30min", "2 days" by sorting suffix strings longest-first to prevent prefix collisions. When the known Synthetic API shape is detected (rollingFiveHourLimit / weeklyTokenLimit / search.hourly keys) it uses slot-positioned mapping so a missing middle lane stays nil instead of shifting labels. Timestamp heuristic: numbers > 1e12 are treated as milliseconds, numbers > 1e9 as seconds, strings are tried with and without fractional seconds. Evidence: SyntheticUsageParser.swift, SyntheticUsageParser.windowSuffixMultipliers sorted by suffix length.

- Repository: steipete/CodexBar
- GitHub: https://github.com/steipete/CodexBar
- Human wiki: https://grok-wiki.com/public/wiki/steipete-codexbar-3494bea25492
- Complete Markdown: https://grok-wiki.com/public/wiki/steipete-codexbar-3494bea25492/llms-full.txt

## Source Files

- `Sources/CodexBarCore/Providers/Synthetic/SyntheticUsageParser.swift`
- `Sources/CodexBarCore/Providers/Synthetic/SyntheticUsageFetcher.swift`
- `Sources/CodexBarCore/Providers/Synthetic/SyntheticProviderDescriptor.swift`
- `Tests/CodexBarTests/VeniceUsageFetcherTests.swift`

---

<details>
<summary>Relevant source files</summary>
The following files were used as context for generating this wiki page:

- [Sources/CodexBarCore/Providers/Synthetic/SyntheticUsageStats.swift](Sources/CodexBarCore/Providers/Synthetic/SyntheticUsageStats.swift)
- [Sources/CodexBarCore/Providers/Synthetic/SyntheticProviderDescriptor.swift](Sources/CodexBarCore/Providers/Synthetic/SyntheticProviderDescriptor.swift)
- [Sources/CodexBarCore/Providers/Synthetic/SyntheticSettingsReader.swift](Sources/CodexBarCore/Providers/Synthetic/SyntheticSettingsReader.swift)
- [Sources/CodexBar/Providers/Synthetic/SyntheticProviderImplementation.swift](Sources/CodexBar/Providers/Synthetic/SyntheticProviderImplementation.swift)
- [Tests/CodexBarTests/SyntheticProviderTests.swift](Tests/CodexBarTests/SyntheticProviderTests.swift)
</details>

# Synthetic Provider — Universal Quota Parser That Handles Any API Shape

The Synthetic provider integrates with the [Synthetic.new](https://synthetic.new) OpenAI-compatible proxy. Because OpenAI-compatible proxies expose wildly different JSON shapes for quota/rate-limit APIs, CodexBar ships `SyntheticUsageParser` — a defensive parser that can interpret nearly any quota response without a fixed schema contract.

This page focuses on the non-obvious mechanics of `SyntheticUsageParser` (defined inline in `SyntheticUsageStats.swift`): its alias-first key lookup, algebraic field derivation, fraction-to-percent auto-detection, duration-string parsing, slot-positioned lane mapping, and timestamp heuristics.

---

## Architecture Overview

```
SyntheticAPIFetchStrategy.fetch()
        │
        ▼
SyntheticUsageFetcher.fetchUsage()   ← GET https://api.synthetic.new/v2/quotas
        │  Bearer token from SYNTHETIC_API_KEY or Keychain
        ▼
SyntheticUsageParser.parse(data:now:)
        │
        ├─ prioritizedQuotaSlots()   ← Known Synthetic shape? Use slot-positioned mapping
        │         (rollingFiveHourLimit / weeklyTokenLimit / search.hourly)
        │
        └─ fallbackQuotaObjects()    ← Unknown shape? Walk 13 key candidates for arrays/dicts
                  │
                  └─ parseQuota()   ← Per-entry: alias lookup → algebra → percent normalization
                            │
                            ▼
                  SyntheticUsageSnapshot (quotas + slottedQuotas?)
                            │
                            ▼
                  toUsageSnapshot() → UsageSnapshot (primary / secondary / tertiary)
```

Sources: [Sources/CodexBarCore/Providers/Synthetic/SyntheticUsageStats.swift:154-187]()

---

## Two-Path Parsing: Known Shape vs. Generic Fallback

### Path 1 — Known Synthetic Shape (Slot-Positioned)

If the response contains any of `rollingFiveHourLimit`, `weeklyTokenLimit`, or `search.hourly` (or any of these nested under a top-level `data` key), `prioritizedQuotaSlots()` fires first and returns a three-element array `[rolling-5h, weekly, search-hourly]`. A missing middle lane is `nil`, not absent — this is the critical invariant.

```swift
// SyntheticUsageStats.swift
let slots: [[String: Any]?] = [rolling, weekly, searchHourly]
return slots.contains(where: { $0 != nil }) ? slots : nil
```

`toUsageSnapshot()` maps slot 0 → `primary`, slot 1 → `secondary`, slot 2 → `tertiary`. If `rollingFiveHourLimit` is absent from the response, `primary` is `nil` and the weekly quota does **not** get promoted to the primary UI lane. This prevents label mismatches when the server omits a quota tier.

The test `preserves slot identity when rolling lane is missing` verifies this: a response with only `weeklyTokenLimit` and `search.hourly` yields `usage.primary == nil`, `usage.secondary != nil`, `usage.tertiary != nil`.

Sources: [Sources/CodexBarCore/Providers/Synthetic/SyntheticUsageStats.swift:192-201](), [Tests/CodexBarTests/SyntheticProviderTests.swift:199-226]()

### Path 2 — Generic Fallback (13-Candidate Key Walk)

When none of the known Synthetic keys appear, `fallbackQuotaObjects()` tries 13 top-level and `data`-nested key names in order:

| Priority | Keys tried |
|----------|-----------|
| 1–6 | `quotas`, `quota`, `limits`, `usage`, `entries`, `subscription` |
| 7–13 | Same six under `root["data"]`, plus `data` itself as a dict |

For each candidate, `extractQuotaObjects()` recursively descends arrays and dicts, collecting any object that contains at least one recognizable numeric field (limit, used, remaining, or percent). Dict keys are iterated in **sorted order** to ensure deterministic extraction.

Sources: [Sources/CodexBarCore/Providers/Synthetic/SyntheticUsageStats.swift:204-227]()

---

## Per-Entry Field Resolution (`parseQuota`)

For each quota object, fields are resolved in three layers, shortest-path first:

### 1. Direct Percent Lookup

`percentUsedKeys` holds 7 aliases tried in order:

```
percentUsed, usedPercent, usagePercent, usage_percent, used_percent, percent_used, percent
```

`percentRemainingKeys` holds 4 aliases. If used-percent is not found but remaining-percent is, `usedPercent = 100 − remaining`.

Sources: [Sources/CodexBarCore/Providers/Synthetic/SyntheticUsageStats.swift:570-585]()

### 2. Algebraic Derivation

If no percent field exists, the parser reads limit / used / remaining via their respective alias sets and derives the missing value algebraically:

```swift
if limit == nil, let used, let remaining { limit = used + remaining }
if used == nil, let limit, let remaining { used = limit - remaining }
if remaining == nil, let limit, let used { remaining = max(0, limit - used) }
if let limit, let used, limit > 0 { usedPercent = (used / limit) * 100 }
```

`limitKeys` covers 12 aliases (`limit`, `messageLimit`, `max_requests`, `quota`, `total`, `capacity`, `allowance`, …); `usedKeys` covers 11 (`used`, `usage`, `consumed`, `spent`, …); `remainingKeys` covers 4 (`remaining`, `left`, `available`, `balance`).

Sources: [Sources/CodexBarCore/Providers/Synthetic/SyntheticUsageStats.swift:252-269]()

### 3. Percent Auto-Normalization

A percent value ≤ 1 is treated as a 0–1 fraction and multiplied by 100. A value > 1 is kept as-is. This handles APIs that report `0.75` (75%) and APIs that report `75` with the same code path:

```swift
private static func normalizedPercent(_ value: Double?) -> Double? {
    guard let value else { return nil }
    if value <= 1 { return value * 100 }
    return value
}
```

The live Synthetic response uses `tickPercent: 0.05` (meaning 5%), which becomes `nextRegenPercent == 5.0` after normalization. This is verified in the `parses rolling lane tickPercent into primary nextRegenPercent` test.

Sources: [Sources/CodexBarCore/Providers/Synthetic/SyntheticUsageStats.swift:454-458](), [Tests/CodexBarTests/SyntheticProviderTests.swift:149-165]()

---

## Duration String Parsing (`windowMinutes(fromText:)`)

Window durations may arrive as human strings: `"5hr"`, `"30min"`, `"2 days"`, `"1 hour"`. The parser normalizes the string (trim, lowercase, remove spaces) then iterates `windowSuffixMultipliers`.

### The Longest-First Sort

The suffix table is built at init time sorted by `suffix.count` descending:

```swift
private static let windowSuffixMultipliers: [(suffix: String, multiplier: Double)] = {
    let raw: [(String, Double)] = [
        ("minutes", 1), ("minute", 1), ("mins", 1), ("min", 1), ("m", 1),
        ("hours", 60), ("hour", 60), ("hrs", 60), ("hr", 60), ("h", 60),
        ("days", 24 * 60), ("day", 24 * 60), ("d", 24 * 60),
    ]
    return raw
        .sorted { $0.0.count > $1.0.count }
        .map { (suffix: $0.0, multiplier: $0.1) }
}()
```

Without longest-first sorting, `"5hours"` would incorrectly match the single-char suffix `"s"` before reaching `"hours"`. By checking longer suffixes first, multi-letter units always win over their shorter aliases. This is not merely a performance choice — it is a correctness constraint that prevents silent misparses.

The test suite verifies all suffix variants: `"5min"→5`, `"5m"→5`, `"5hr"→300`, `"5h"→300`, `"5hours"→300`, `"2days"→2880`, `"2d"→2880`, `"1 hour"→60`, `"junk"→nil`.

Sources: [Sources/CodexBarCore/Providers/Synthetic/SyntheticUsageStats.swift:378-387](), [Tests/CodexBarTests/SyntheticProviderTests.swift:185-196]()

---

## Timestamp Heuristics (`dateValue`)

The `firstDate(in:keys:)` helper resolves reset/expiry timestamps from 18 key aliases (`resetAt`, `reset_at`, `resetsAt`, `renewAt`, `nextTickAt`, `periodEnd`, `expiresAt`, …). Each candidate value is passed through `dateValue()`:

```swift
private static func dateValue(_ raw: Any) -> Date? {
    if let number = self.doubleValue(raw) {
        if number > 1_000_000_000_000 { return Date(timeIntervalSince1970: number / 1000) }
        if number > 1_000_000_000    { return Date(timeIntervalSince1970: number) }
    }
    if let string = raw as? String {
        if let number = Double(string.trimmingCharacters(in: .whitespacesAndNewlines)) {
            return self.dateValue(number)  // numeric string → re-enter heuristic
        }
        if let date = SyntheticTimestampParser.parse(string) { return date }
    }
    return nil
}
```

The thresholds distinguish milliseconds (> 1 × 10¹²) from seconds (> 1 × 10⁹) without any explicit type tag. The `SyntheticTimestampParser` tries ISO 8601 with fractional seconds first, then plain ISO 8601 without — covering both `"2026-04-17T04:30:01.494Z"` and `"2026-04-17T03:44:11Z"`. The formatter pair is guarded by `NSLock` since ISO 8601 formatters are not thread-safe.

Sources: [Sources/CodexBarCore/Providers/Synthetic/SyntheticUsageStats.swift:529-547](), [Sources/CodexBarCore/Providers/Synthetic/SyntheticUsageStats.swift:129-152]()

---

## Reset Description: Live vs. Static

There is a subtle intentional asymmetry in `parseQuota`: when `resetsAt` is populated, `resetDescription` is set to `nil`:

```swift
// Leave resetDescription nil when resetsAt is set so the UI rebuilds the countdown each render
// against the current clock instead of freezing a stale "in Xm" string at parse time.
let resetDescription = resetsAt == nil ? self.windowDescription(minutes: windowMinutes) : nil
```

When a reset timestamp is known, the UI layer computes a live countdown on every render. Only when no timestamp is available does the parser generate a static fallback string like `"5 hours window"`. This prevents stale countdown strings if the parsed snapshot is cached between refreshes.

Sources: [Sources/CodexBarCore/Providers/Synthetic/SyntheticUsageStats.swift:277-279]()

---

## Cost Snapshot Extraction

When a quota object contains `maxCredits` or `max_credits`, `providerCost()` produces a `ProviderCostSnapshot` with USD amounts. It strips currency symbols and commas before parsing (`"$36.00"` → `36.0`). The `used` amount is resolved in priority order: explicit `usedCredits` field → `limit − remainingCredits` → `(usedPercent / 100) × limit`. `nextRegenCredits` (alias `next_regen_credits`) is surfaced as `nextRegenAmount` for regeneration indicators.

In the live Synthetic API shape, `weeklyTokenLimit.remainingCredits = "$35.30"` with `maxCredits = "$36.00"` yields `used = 0.70`. The test `parses live root level rolling and weekly quotas` verifies `providerCost?.used ≈ 0.7`.

Sources: [Sources/CodexBarCore/Providers/Synthetic/SyntheticUsageStats.swift:403-429](), [Tests/CodexBarTests/SyntheticProviderTests.swift:93-146]()

---

## Provider Configuration

The provider is registered as non-primary and disabled by default. Its three display lanes are labelled **Five-hour quota**, **Weekly tokens**, and **Search hourly** — matching the three known Synthetic API slots. Token cost reporting is disabled (`supportsTokenCost: false`). The API key is resolved from the `SYNTHETIC_API_KEY` environment variable or from `~/.codexbar/config.json`; the settings reader strips surrounding single or double quotes to tolerate copy-paste artefacts.

Sources: [Sources/CodexBarCore/Providers/Synthetic/SyntheticProviderDescriptor.swift:8-39](), [Sources/CodexBarCore/Providers/Synthetic/SyntheticSettingsReader.swift:13-28]()

---

## Summary

`SyntheticUsageParser` is a schema-free defensive parser: it tries dozens of key aliases, applies algebraic field derivation when direct values are absent, auto-detects 0–1 vs. 0–100 percent encoding, and handles arbitrary timestamp and duration formats. The one place it departs from pure genericity is the `prioritizedQuotaSlots` path — when the known Synthetic API shape is detected, slot-positional mapping guarantees that a missing quota tier stays `nil` in its UI lane rather than silently promoting the next lane, preventing label mismatches that would otherwise be invisible from the outside. The full parsing logic, including all alias tables and edge cases, lives in `SyntheticUsageStats.swift`.

Sources: [Sources/CodexBarCore/Providers/Synthetic/SyntheticUsageStats.swift:154-387]()
