{"record":{"id":"1012dba32936b483","repo":"headroomlabs-ai/headroom","slug":"model-model-does-not-have-batch-output-pricing","errorCode":null,"errorMessage":"Model '{model}' does not have batch output pricing","messagePattern":"Model '(.+?)' does not have batch output pricing","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"headroom/pricing/registry.py","lineNumber":173,"sourceCode":"            }\n            total_cost += cached_cost\n\n        # Batch input tokens\n        if batch_input_tokens > 0:\n            if pricing.batch_input_per_1m is None:\n                raise ValueError(f\"Model '{model}' does not have batch input pricing\")\n            batch_input_cost = (batch_input_tokens / 1_000_000) * pricing.batch_input_per_1m\n            breakdown[\"batch_input\"] = {\n                \"tokens\": batch_input_tokens,\n                \"rate_per_1m\": pricing.batch_input_per_1m,\n                \"cost_usd\": batch_input_cost,\n            }\n            total_cost += batch_input_cost\n\n        # Batch output tokens\n        if batch_output_tokens > 0:\n            if pricing.batch_output_per_1m is None:\n                raise ValueError(f\"Model '{model}' does not have batch output pricing\")\n            batch_output_cost = (batch_output_tokens / 1_000_000) * pricing.batch_output_per_1m\n            breakdown[\"batch_output\"] = {\n                \"tokens\": batch_output_tokens,\n                \"rate_per_1m\": pricing.batch_output_per_1m,\n                \"cost_usd\": batch_output_cost,\n            }\n            total_cost += batch_output_cost\n\n        return CostEstimate(\n            cost_usd=total_cost,\n            breakdown=breakdown,\n            pricing_date=self.last_updated,\n            is_stale=self.is_stale(),\n            warning=self.staleness_warning(),\n        )\n","sourceCodeStart":155,"sourceCodeEnd":189,"githubUrl":"https://github.com/headroomlabs-ai/headroom/blob/322425c43bffde1ed0b64fecf3cf5951565dd82b/headroom/pricing/registry.py#L155-L189","documentation":"Thrown by the pricing registry's cost estimator when a request includes batch output tokens but the model's pricing entry has no batch_output_per_1m rate (it is None). Pricing records only carry batch output rates for models that publish discounted Batch API output pricing, so asking to price batch output for any other model is rejected rather than silently priced at zero. It mirrors the batch-input check directly above it in the same method.","triggerScenarios":"Calling registry.estimate/estimate_cost with batch_output_tokens > 0 for a model whose pricing row has batch_output_per_1m=None (e.g. a model that only lists batch input pricing, or a custom pricing entry added without the batch output field). Passing batch usage rows from a Batch API job through the standard pricing path.","commonSituations":"Custom/self-hosted model pricing added to the registry without batch fields; a provider that discounts batch input but not output; using a batch report against an older pricing snapshot that predates batch output rates.","solutions":["Check pricing.batch_output_per_1m is not None before including batch_output_tokens in the call, or pass batch_output_tokens=0","Update the model's pricing entry (registry data / pricing file) to include a batch output rate if the provider actually publishes one","If the model truly has no batch output discount, route batch output tokens through the regular output_tokens argument so they are priced at the standard rate"],"exampleFix":"// before\nestimate = registry.estimate(\n    model=\"my-model\",\n    input_tokens=1000,\n    output_tokens=0,\n    batch_input_tokens=50000,\n    batch_output_tokens=8000,   # raises: no batch output pricing\n)\n\n// after\npricing = registry.get_pricing(\"my-model\")\nbatch_out = 8000 if pricing.batch_output_per_1m is not None else 0\nestimate = registry.estimate(\n    model=\"my-model\",\n    input_tokens=1000,\n    output_tokens=0 if pricing.batch_output_per_1m is not None else 8000,\n    batch_input_tokens=50000,\n    batch_output_tokens=batch_out,\n)","handlingStrategy":"validation","validationCode":"from headroom.pricing.registry import PricingRegistry\n\npricing = registry.get_pricing(model)\nhas_batch_out = pricing.batch_output_per_1m is not None\nestimate = registry.estimate(\n    model=model,\n    input_tokens=in_tok,\n    output_tokens=0 if has_batch_out else out_tok,\n    batch_input_tokens=batch_in if pricing.batch_input_per_1m is not None else 0,\n    batch_output_tokens=batch_out if has_batch_out else 0,\n)","typeGuard":"def supports_batch_output_pricing(pricing) -> bool:\n    return pricing.batch_output_per_1m is not None","tryCatchPattern":"try:\n    estimate = registry.estimate(...)\nexcept ValueError as e:\n    if 'batch output pricing' in str(e):\n        # re-price batch output at standard rates\n        ...\n    raise","preventionTips":["Check pricing.batch_*_per_1m fields before passing batch token counts","Keep registry pricing data in sync with provider batch pricing pages","Treat None pricing fields as 'feature unsupported', not 'free'"],"tags":["pricing","batch-api","validation"],"backgroundTag":null,"analyzedSha":"322425c43bffde1ed0b64fecf3cf5951565dd82b","analyzedAt":"2026-08-15T01:03:05.481Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}