JuliusBrussee/caveman · error · CatalogError

: currency must be USD

Error message

{label}: currency must be USD

What it means

A row's `currency` is not the literal string "USD". All prices in this catalog are denominated in USD — cross-currency rows would corrupt the no-fake-savings cost math — so the validator pins the currency exactly. Thrown from validate_row.

Solutions

  1. Set `currency: USD` exactly (uppercase)
  2. If your source price is in another currency, convert it to USD and cite the USD figure from the provider's pricing page (usually switchable to USD)
  3. Remove any stray currency field value like null or an empty string and write USD

Example fix

# before
currency: usd

# after
currency: USD
Defensive patterns

Strategy: validation

Validate before calling

def currency_ok(row: dict) -> bool:
    return row.get("currency") == "USD"

Type guard

def is_usd(value: object) -> bool:
    return value == "USD"

Try / catch

try:
    validate_catalog()
except CatalogError as e:
    if "currency must be USD" in str(e):
        normalize_currency(str(e))

Prevention

When it happens

Trigger: A row has `currency: usd` (lowercase), `currency: EUR`, `currency: null`, or omits any currency normalization, e.g. after copying a price from a provider page quoting in another currency.

Common situations: Copying a European provider/VAT-inclusive EUR price; a lowercase typo; a sync script carrying through a currency field from a third-party models dataset; new row authored from a non-US pricing page.

Understand the failure class

Background: Invalid enum value errors: "Unknown type", "Invalid scope", "must be one of" — when a string is not on the library's allowed list — this error's family across 23 libraries.

Related errors


AI-assisted analysis of JuliusBrussee/caveman@3ee70a1026 (2026-09-20). Data as JSON: /api/errors/b160282fa14ce435. Report an issue: GitHub.

Appendix: source

Thrown at shared/provider-catalog/validate_catalog.py:131

    return parsed.astimezone(timezone.utc)


def validate_row(row: dict[str, Any], label: str, now: datetime) -> tuple[str, str, str]:
    unknown = set(row) - TOP_LEVEL_KEYS
    missing = REQUIRED_KEYS - set(row)
    if unknown:
        raise CatalogError(f"{label}: unknown field(s): {', '.join(sorted(unknown))}")
    if missing:
        raise CatalogError(f"{label}: missing field(s): {', '.join(sorted(missing))}")

    identity: list[str] = []
    for field in ("provider", "model", "region"):
        value = row[field]
        if not isinstance(value, str) or not IDENTIFIER.fullmatch(value):
            raise CatalogError(f"{label}: {field} must be a safe non-empty identifier")
        identity.append(value)
    if row["currency"] != "USD":
        raise CatalogError(f"{label}: currency must be USD")

    pricing = row["pricing"]
    if not isinstance(pricing, dict):
        raise CatalogError(f"{label}: pricing must be an object")
    pricing_unknown = set(pricing) - PRICING_KEYS
    if pricing_unknown:
        raise CatalogError(f"{label}: unknown pricing field(s): {', '.join(sorted(pricing_unknown))}")
    for required in ("input_per_million", "output_per_million"):
        if required not in pricing:
            raise CatalogError(f"{label}: pricing.{required} is required")
    for field, value in pricing.items():
        if value is None or field == "long_context_threshold_inclusive":
            if field == "long_context_threshold_inclusive" and value is not None and not isinstance(value, bool):
                raise CatalogError(f"{label}: pricing.{field} must be boolean or null")
            continue
        if isinstance(value, bool) or not isinstance(value, (int, float)):
            raise CatalogError(f"{label}: pricing.{field} must be numeric or null")
        if not math.isfinite(value) or value < 0:

View on GitHub (pinned to 3ee70a1026)