datasheet

package
v1.11.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 29, 2026 License: Apache-2.0 Imports: 24 Imported by: 0

Documentation

Overview

Package pricing owns the pricing/model-parameters catalog (compartments A + B + E): canonical pricing rows fetched from the upstream datasheet, per-provider datasheet-derived model views, supported request types and parameters, and scoped pricing overrides. It also computes per-response cost.

The package performs no list-models I/O — that's the live store's domain. The hourly sync ticker lives on the composer (ModelCatalog), not here; the composer calls SyncFromURL / LoadFromDB / Sync*ModelParams*. Reads are hot-path and lock-free where possible.

Index

Constants

View Source
const (
	DefaultURL                    = "https://getbifrost.ai/datasheet"
	DefaultModelParametersURL     = "https://getbifrost.ai/datasheet/model-parameters"
	DefaultSyncInterval           = 24 * time.Hour
	DefaultPricingTimeout         = 60 * time.Second
	DefaultModelParametersTimeout = 45 * time.Second
)

Defaults for sync configuration and timeouts. Exposed so the composer can fall back to these when the framework Config leaves fields nil.

View Source
const (
	TokenTierAbove272K = 272000
	TokenTierAbove200K = 200000
	TokenTierAbove128K = 128000
	TokenTierAbove100K = 100000
)

Tier boundaries for tiered token pricing. Matches the upstream datasheet keys (input_cost_per_token_above_<N>k_tokens).

Variables

NewHTTPClient builds the client for every catalog feed download (pricing, model parameters, MCP library). It is a var, not a direct call to network.NewPrivateNetworkHTTPClient, only so tests can wrap the transport: the guarded client builds its own *http.Transport, so swapping http.DefaultTransport no longer intercepts these fetches. Without this hook every LoadConfig in transports/bifrost-http/lib tests downloads the full public datasheets (~29MB), which took that package from minutes to ~35 min under -race in CI. Production code must not reassign it.

Functions

func FilePathFromURL added in v1.6.0

func FilePathFromURL(parsed *url.URL) string

FilePathFromURL resolves a parsed file:// URL to a filesystem path, supporting both absolute and relative references. Go's url.Parse scatters a relative path across different fields depending on its form, so we reassemble it here:

  • file:./x.json or file:x.json -> Opaque ("./x.json")
  • file://./x.json -> Host (".") + Path ("/x.json") = "./x.json"
  • file:///abs/x.json -> Path ("/abs/x.json")
  • file://localhost/abs/x.json -> Path ("/abs/x.json")

Relative paths resolve against the process working directory, matching how the sqlite config store treats a relative "path" value.

Exported because every air-gapped source in the catalog resolves file:// URLs the same way: the pricing datasheet, the model parameters datasheet, and the MCP library catalog (modelcatalog.fetchMCPLibrary).

func IsEmptyModelCapabilities added in v1.6.0

func IsEmptyModelCapabilities(ov *schemas.ModelCapabilities) bool

IsEmptyModelCapabilities reports whether no field on the override struct is set. Compared against the zero value so new fields need no maintenance here.

Types

type BatchCostDetails added in v1.6.0

type BatchCostDetails struct {
	Cost                      float64
	Priced                    bool
	ProviderCostUsed          bool
	InputCostPerTokenBatches  *float64
	OutputCostPerTokenBatches *float64
}

BatchCostDetails captures the rate inputs used to price a batch result row. InputCostPerTokenBatches/OutputCostPerTokenBatches are the rates actually applied — the catalog's explicit batch rate when set, otherwise defaultBatchPricingRatio of the standard rate. There is no field marking which case occurred; callers that need to distinguish an authoritative catalog rate from an assumed default must compare against the standard rate themselves.

type CatalogPricingOverrides added in v1.5.9

type CatalogPricingOverrides struct {
	// AppliedID is the override that wins under the global/provider scopes
	// only — the scopes that apply to every request regardless of caller.
	// Empty when nothing global/provider-scoped matches.
	AppliedID string
	// AppliedPatch is that winner's option patch; nil when AppliedID is empty.
	AppliedPatch *Options
	// Matching lists every override matching (model, provider) regardless of
	// scope or mode, most-specific-first, for informational display. Includes
	// the applied one.
	Matching []Override
}

CatalogPricingOverrides summarizes how scoped overrides affect one (model, provider) row of the management model catalog.

type Config

type Config struct {
	URL                string
	ModelParametersURL string
	SyncInterval       time.Duration
}

Config groups the values the composer hands to New / UpdateSyncConfig. Zero values fall back to the Default* constants.

type Entry

type Entry struct {
	BaseModel string `json:"base_model,omitempty"`
	Provider  string `json:"provider"`
	Mode      string `json:"mode"`

	ContextLength   *int                  `json:"context_length,omitempty"`
	MaxInputTokens  *int                  `json:"max_input_tokens,omitempty"`
	MaxOutputTokens *int                  `json:"max_output_tokens,omitempty"`
	Architecture    *schemas.Architecture `json:"architecture,omitempty"`
	IsDeprecated    bool                  `json:"is_deprecated,omitempty"`

	// AdditionalAttributes carries editorial metadata stored on the pricing
	// row (e.g. description). Populated from the DB read path only; the
	// json:"-" tag prevents URL datasheet payloads from ever feeding into
	// this field via json.Unmarshal.
	AdditionalAttributes map[string]string `json:"-"`

	Options
}

Entry represents a single model's pricing information. Field names and JSON tags match the datasheet schema exactly. AdditionalAttributes carries editorial metadata stored on the pricing row — never populated from the URL datasheet, only from DB reads via the management API.

func (*Entry) UnmarshalJSON

func (p *Entry) UnmarshalJSON(data []byte) error

UnmarshalJSON handles the special case where search_context_cost_per_query may arrive as either a plain float64 or a tiered object {"search_context_size_low":…, "search_context_size_medium":…, "search_context_size_high":…}.

type LookupScopes

type LookupScopes struct {
	UserID        string
	VirtualKeyID  string
	SelectedKeyID string
	Provider      string
	// BilledAt is the instant used to decide peak vs off-peak for models that
	// carry a PeakHours schedule. It is the request's START time, not the
	// completion time: that keeps pricing deterministic and reproducible, makes
	// streaming and non-streaming agree, and matches the timestamp users see in
	// logs. A long stream that crosses a window boundary bills entirely at its
	// start-time rate. The zero value means "unknown" and falls back to the
	// wall clock at evaluation time.
	BilledAt time.Time
}

LookupScopes carries the runtime identifiers used to resolve scoped pricing overrides during cost calculation.

func LookupScopesFromContext

func LookupScopesFromContext(ctx *schemas.BifrostContext, provider string) *LookupScopes

LookupScopesFromContext builds a LookupScopes from a BifrostContext. Reads the governance virtual key ID (not the raw VK token), the selected key ID, and the resolved calling user ID. provider should be the provider name string (e.g. "openai"); pass "" if unavailable. Returns nil only when ctx is nil. An empty scopes value is still returned when all fields are empty so global-scope overrides remain evaluable.

NOT SAFE in a goroutine — reads from ctx which is cancelled when the request ends. Call synchronously in PostHooks and pass the result by value to anything that may outlive the request.

type MatchType

type MatchType string

MatchType controls how an override pattern is matched against model names.

const (
	MatchTypeExact    MatchType = "exact"
	MatchTypeWildcard MatchType = "wildcard"
)

type Options

type Options struct {
	// Costs - Text
	InputCostPerToken           *float64 `json:"input_cost_per_token,omitempty"`
	OutputCostPerToken          *float64 `json:"output_cost_per_token,omitempty"`
	InputCostPerTokenBatches    *float64 `json:"input_cost_per_token_batches,omitempty"`
	OutputCostPerTokenBatches   *float64 `json:"output_cost_per_token_batches,omitempty"`
	InputCostPerTokenPriority   *float64 `json:"input_cost_per_token_priority,omitempty"`
	OutputCostPerTokenPriority  *float64 `json:"output_cost_per_token_priority,omitempty"`
	InputCostPerTokenUltrafast  *float64 `json:"input_cost_per_token_ultrafast,omitempty"`
	OutputCostPerTokenUltrafast *float64 `json:"output_cost_per_token_ultrafast,omitempty"`
	InputCostPerTokenFlex       *float64 `json:"input_cost_per_token_flex,omitempty"`
	OutputCostPerTokenFlex      *float64 `json:"output_cost_per_token_flex,omitempty"`
	// Decision requests on a model whose row is another mode (OpenAI's gpt-6-luna
	// is a chat row served natively on /v1/decisions). See computeDecisionCost.
	InputCostPerTokenDecisions  *float64 `json:"input_cost_per_token_decisions,omitempty"`
	OutputCostPerTokenDecisions *float64 `json:"output_cost_per_token_decisions,omitempty"`
	// Fast mode (Anthropic research preview, speed:"fast" on Opus 4.6/4.7/4.8).
	// Flat rate across the full context window — no 128k/200k/272k tiering.
	InputCostPerTokenFast  *float64 `json:"input_cost_per_token_fast,omitempty"`
	OutputCostPerTokenFast *float64 `json:"output_cost_per_token_fast,omitempty"`
	InputCostPerCharacter  *float64 `json:"input_cost_per_character,omitempty"`
	// Costs - 128k Tier
	InputCostPerTokenAbove128kTokens          *float64 `json:"input_cost_per_token_above_128k_tokens,omitempty"`
	InputCostPerImageAbove128kTokens          *float64 `json:"input_cost_per_image_above_128k_tokens,omitempty"`
	InputCostPerVideoPerSecondAbove128kTokens *float64 `json:"input_cost_per_video_per_second_above_128k_tokens,omitempty"`
	InputCostPerAudioPerSecondAbove128kTokens *float64 `json:"input_cost_per_audio_per_second_above_128k_tokens,omitempty"`
	OutputCostPerTokenAbove128kTokens         *float64 `json:"output_cost_per_token_above_128k_tokens,omitempty"`
	// Costs - 100k Tier
	InputCostPerTokenAbove100kTokens  *float64 `json:"input_cost_per_token_above_100k_tokens,omitempty"`
	OutputCostPerTokenAbove100kTokens *float64 `json:"output_cost_per_token_above_100k_tokens,omitempty"`
	// Costs - 200k Tier
	InputCostPerTokenAbove200kTokens          *float64 `json:"input_cost_per_token_above_200k_tokens,omitempty"`
	InputCostPerTokenAbove200kTokensPriority  *float64 `json:"input_cost_per_token_above_200k_tokens_priority,omitempty"`
	OutputCostPerTokenAbove200kTokens         *float64 `json:"output_cost_per_token_above_200k_tokens,omitempty"`
	OutputCostPerTokenAbove200kTokensPriority *float64 `json:"output_cost_per_token_above_200k_tokens_priority,omitempty"`
	// Costs - 272k Tier
	InputCostPerTokenAbove272kTokens           *float64 `json:"input_cost_per_token_above_272k_tokens,omitempty"`
	InputCostPerTokenAbove272kTokensPriority   *float64 `json:"input_cost_per_token_above_272k_tokens_priority,omitempty"`
	InputCostPerTokenFlexAbove272kTokens       *float64 `json:"input_cost_per_token_flex_above_272k_tokens,omitempty"`
	OutputCostPerTokenAbove272kTokens          *float64 `json:"output_cost_per_token_above_272k_tokens,omitempty"`
	OutputCostPerTokenAbove272kTokensPriority  *float64 `json:"output_cost_per_token_above_272k_tokens_priority,omitempty"`
	OutputCostPerTokenFlexAbove272kTokens      *float64 `json:"output_cost_per_token_flex_above_272k_tokens,omitempty"`
	InputCostPerTokenAbove272kTokensUltrafast  *float64 `json:"input_cost_per_token_above_272k_tokens_ultrafast,omitempty"`
	OutputCostPerTokenAbove272kTokensUltrafast *float64 `json:"output_cost_per_token_above_272k_tokens_ultrafast,omitempty"`

	// Costs - Cache
	CacheCreationInputTokenCost                        *float64 `json:"cache_creation_input_token_cost,omitempty"`
	CacheReadInputTokenCost                            *float64 `json:"cache_read_input_token_cost,omitempty"`
	CacheCreationInputTokenCostAbove200kTokens         *float64 `json:"cache_creation_input_token_cost_above_200k_tokens,omitempty"`
	CacheReadInputTokenCostAbove200kTokens             *float64 `json:"cache_read_input_token_cost_above_200k_tokens,omitempty"`
	CacheReadInputTokenCostAbove200kTokensPriority     *float64 `json:"cache_read_input_token_cost_above_200k_tokens_priority,omitempty"`
	CacheCreationInputTokenCostAbove1hr                *float64 `json:"cache_creation_input_token_cost_above_1hr,omitempty"`
	CacheCreationInputTokenCostAbove1hrAbove200kTokens *float64 `json:"cache_creation_input_token_cost_above_1hr_above_200k_tokens,omitempty"`
	CacheCreationInputTokenCostAbove100kTokens         *float64 `json:"cache_creation_input_token_cost_above_100k_tokens,omitempty"`
	CacheReadInputTokenCostAbove100kTokens             *float64 `json:"cache_read_input_token_cost_above_100k_tokens,omitempty"`
	CacheCreationInputTokenCostAbove1hrAbove100kTokens *float64 `json:"cache_creation_input_token_cost_above_1hr_above_100k_tokens,omitempty"`
	CacheCreationInputAudioTokenCost                   *float64 `json:"cache_creation_input_audio_token_cost,omitempty"`
	CacheReadInputTokenCostPriority                    *float64 `json:"cache_read_input_token_cost_priority,omitempty"`
	CacheReadInputTokenCostUltrafast                   *float64 `json:"cache_read_input_token_cost_ultrafast,omitempty"`
	CacheReadInputTokenCostFlex                        *float64 `json:"cache_read_input_token_cost_flex,omitempty"`
	CacheReadInputImageTokenCost                       *float64 `json:"cache_read_input_image_token_cost,omitempty"`
	CacheReadInputTokenCostAbove272kTokens             *float64 `json:"cache_read_input_token_cost_above_272k_tokens,omitempty"`
	CacheReadInputTokenCostAbove272kTokensPriority     *float64 `json:"cache_read_input_token_cost_above_272k_tokens_priority,omitempty"`
	CacheReadInputTokenCostFlexAbove272kTokens         *float64 `json:"cache_read_input_token_cost_flex_above_272k_tokens,omitempty"`
	CacheReadInputTokenCostAbove272kTokensUltrafast    *float64 `json:"cache_read_input_token_cost_above_272k_tokens_ultrafast,omitempty"`
	// OpenAI cache-write (cache-creation) tiered rates, added with gpt-5.6.
	CacheCreationInputTokenCostAbove272kTokens          *float64 `json:"cache_creation_input_token_cost_above_272k_tokens,omitempty"`
	CacheCreationInputTokenCostFlex                     *float64 `json:"cache_creation_input_token_cost_flex,omitempty"`
	CacheCreationInputTokenCostFlexAbove272kTokens      *float64 `json:"cache_creation_input_token_cost_flex_above_272k_tokens,omitempty"`
	CacheCreationInputTokenCostPriority                 *float64 `json:"cache_creation_input_token_cost_priority,omitempty"`
	CacheCreationInputTokenCostAbove272kTokensPriority  *float64 `json:"cache_creation_input_token_cost_above_272k_tokens_priority,omitempty"`
	CacheCreationInputTokenCostUltrafast                *float64 `json:"cache_creation_input_token_cost_ultrafast,omitempty"`
	CacheCreationInputTokenCostAbove272kTokensUltrafast *float64 `json:"cache_creation_input_token_cost_above_272k_tokens_ultrafast,omitempty"`
	// Fast mode (Anthropic) cache rates — flat across the full context window, no tiering.
	CacheCreationInputTokenCostFast         *float64 `json:"cache_creation_input_token_cost_fast,omitempty"`
	CacheCreationInputTokenCostAbove1hrFast *float64 `json:"cache_creation_input_token_cost_above_1hr_fast,omitempty"`
	CacheReadInputTokenCostFast             *float64 `json:"cache_read_input_token_cost_fast,omitempty"`

	// Costs - Image
	InputCostPerImage                             *float64 `json:"input_cost_per_image,omitempty"`
	InputCostPerPixel                             *float64 `json:"input_cost_per_pixel,omitempty"`
	OutputCostPerImage                            *float64 `json:"output_cost_per_image,omitempty"`
	OutputCostPerPixel                            *float64 `json:"output_cost_per_pixel,omitempty"`
	OutputCostPerImagePremiumImage                *float64 `json:"output_cost_per_image_premium_image,omitempty"`
	OutputCostPerImageAbove512x512Pixels          *float64 `json:"output_cost_per_image_above_512_and_512_pixels,omitempty"`
	OutputCostPerImageAbove512x512PixelsPremium   *float64 `json:"output_cost_per_image_above_512_and_512_pixels_and_premium_image,omitempty"`
	OutputCostPerImageAbove1024x1024Pixels        *float64 `json:"output_cost_per_image_above_1024_and_1024_pixels,omitempty"`
	OutputCostPerImageAbove1024x1024PixelsPremium *float64 `json:"output_cost_per_image_above_1024_and_1024_pixels_and_premium_image,omitempty"`
	OutputCostPerImageAbove1024x1536Pixels        *float64 `json:"output_cost_per_image_above_1024_and_1536_pixels,omitempty"`
	OutputCostPerImageAbove1536x1024Pixels        *float64 `json:"output_cost_per_image_above_1536_and_1024_pixels,omitempty"`
	OutputCostPerImageAbove2048x2048Pixels        *float64 `json:"output_cost_per_image_above_2048_and_2048_pixels,omitempty"`
	OutputCostPerImageAbove4096x4096Pixels        *float64 `json:"output_cost_per_image_above_4096_and_4096_pixels,omitempty"`
	OutputCostPerImageAbove4Megapixels            *float64 `json:"output_cost_per_image_above_4_megapixels,omitempty"`
	OutputCostPerImageAbove8Megapixels            *float64 `json:"output_cost_per_image_above_8_megapixels,omitempty"`
	OutputCostPerImageAbove16Megapixels           *float64 `json:"output_cost_per_image_above_16_megapixels,omitempty"`
	OutputCostPerImageAbove32Megapixels           *float64 `json:"output_cost_per_image_above_32_megapixels,omitempty"`
	OutputCostPerImageAbove64Megapixels           *float64 `json:"output_cost_per_image_above_64_megapixels,omitempty"`
	OutputCostPerImageLowQuality                  *float64 `json:"output_cost_per_image_low_quality,omitempty"`
	OutputCostPerImageMediumQuality               *float64 `json:"output_cost_per_image_medium_quality,omitempty"`
	OutputCostPerImageHighQuality                 *float64 `json:"output_cost_per_image_high_quality,omitempty"`
	OutputCostPerImageAutoQuality                 *float64 `json:"output_cost_per_image_auto_quality,omitempty"`
	InputCostPerImageToken                        *float64 `json:"input_cost_per_image_token,omitempty"`
	OutputCostPerImageToken                       *float64 `json:"output_cost_per_image_token,omitempty"`

	// Costs - Image, joint size and quality. These are the most specific
	// per-image rates: they win over the quality-only and size-only rates
	// above, since upstream prices size and quality together.
	OutputCostPerImageAbove1024x1024PixelsLowQuality      *float64 `json:"output_cost_per_image_above_1024_and_1024_pixels_low_quality,omitempty"`
	OutputCostPerImageAbove1024x1536PixelsLowQuality      *float64 `json:"output_cost_per_image_above_1024_and_1536_pixels_low_quality,omitempty"`
	OutputCostPerImageAbove1536x1024PixelsLowQuality      *float64 `json:"output_cost_per_image_above_1536_and_1024_pixels_low_quality,omitempty"`
	OutputCostPerImageAbove1024x1024PixelsMediumQuality   *float64 `json:"output_cost_per_image_above_1024_and_1024_pixels_medium_quality,omitempty"`
	OutputCostPerImageAbove1024x1536PixelsMediumQuality   *float64 `json:"output_cost_per_image_above_1024_and_1536_pixels_medium_quality,omitempty"`
	OutputCostPerImageAbove1536x1024PixelsMediumQuality   *float64 `json:"output_cost_per_image_above_1536_and_1024_pixels_medium_quality,omitempty"`
	OutputCostPerImageAbove1024x1024PixelsHighQuality     *float64 `json:"output_cost_per_image_above_1024_and_1024_pixels_high_quality,omitempty"`
	OutputCostPerImageAbove1024x1536PixelsHighQuality     *float64 `json:"output_cost_per_image_above_1024_and_1536_pixels_high_quality,omitempty"`
	OutputCostPerImageAbove1536x1024PixelsHighQuality     *float64 `json:"output_cost_per_image_above_1536_and_1024_pixels_high_quality,omitempty"`
	OutputCostPerImageAbove1024x1024PixelsStandardQuality *float64 `json:"output_cost_per_image_above_1024_and_1024_pixels_standard_quality,omitempty"`
	OutputCostPerImageAbove1024x1536PixelsStandardQuality *float64 `json:"output_cost_per_image_above_1024_and_1536_pixels_standard_quality,omitempty"`
	OutputCostPerImageAbove1536x1024PixelsStandardQuality *float64 `json:"output_cost_per_image_above_1536_and_1024_pixels_standard_quality,omitempty"`

	// Costs - Audio/Video
	InputCostPerAudioToken      *float64 `json:"input_cost_per_audio_token,omitempty"`
	InputCostPerAudioPerSecond  *float64 `json:"input_cost_per_audio_per_second,omitempty"`
	InputCostPerSecond          *float64 `json:"input_cost_per_second,omitempty"`
	InputCostPerVideoPerSecond  *float64 `json:"input_cost_per_video_per_second,omitempty"`
	OutputCostPerAudioToken     *float64 `json:"output_cost_per_audio_token,omitempty"`
	OutputCostPerVideoPerSecond *float64 `json:"output_cost_per_video_per_second,omitempty"`
	OutputCostPerSecond         *float64 `json:"output_cost_per_second,omitempty"`

	// Azure Voice Live rates, used on Voice Live turns in place of the matching rates above when set. The
	// speech rates price the audio Azure Speech handled.
	InputCostPerTokenVoiceLive                     *float64 `json:"input_cost_per_token_voice_live,omitempty"`
	OutputCostPerTokenVoiceLive                    *float64 `json:"output_cost_per_token_voice_live,omitempty"`
	CacheReadInputTokenCostVoiceLive               *float64 `json:"cache_read_input_token_cost_voice_live,omitempty"`
	InputCostPerAudioTokenVoiceLive                *float64 `json:"input_cost_per_audio_token_voice_live,omitempty"`
	OutputCostPerAudioTokenVoiceLive               *float64 `json:"output_cost_per_audio_token_voice_live,omitempty"`
	InputCostPerStandardSpeechAudioTokenVoiceLive  *float64 `json:"input_cost_per_standard_speech_audio_token_voice_live,omitempty"`
	OutputCostPerStandardSpeechAudioTokenVoiceLive *float64 `json:"output_cost_per_standard_speech_audio_token_voice_live,omitempty"`
	InputCostPerCustomSpeechAudioTokenVoiceLive    *float64 `json:"input_cost_per_custom_speech_audio_token_voice_live,omitempty"`
	OutputCostPerCustomSpeechAudioTokenVoiceLive   *float64 `json:"output_cost_per_custom_speech_audio_token_voice_live,omitempty"`

	// Resolution-banded video output rates, matched on the short edge of the response's
	// size. Absent band falls back to OutputCostPerVideoPerSecond, then OutputCostPerSecond.
	OutputCostPerVideoPerSecond480p  *float64 `json:"output_cost_per_video_per_second_480p,omitempty"`
	OutputCostPerVideoPerSecond720p  *float64 `json:"output_cost_per_video_per_second_720p,omitempty"`
	OutputCostPerVideoPerSecond1024p *float64 `json:"output_cost_per_video_per_second_1024p,omitempty"`
	OutputCostPerVideoPerSecond1080p *float64 `json:"output_cost_per_video_per_second_1080p,omitempty"`
	OutputCostPerVideoPerSecond4k    *float64 `json:"output_cost_per_video_per_second_4k,omitempty"`

	// Per-token video output rate, for providers that meter video in tokens (BytePlus Seedance).
	// Takes precedence over OutputCostPerToken for video output tokens.
	OutputCostPerVideoToken *float64 `json:"output_cost_per_video_token,omitempty"`

	// Costs - Other.
	//
	// SearchContextCostPerQuery is stored as a single float64, but the upstream datasheet
	// represents it as a tiered object. See Entry.UnmarshalJSON.
	SearchContextCostPerQuery *float64 `json:"search_context_cost_per_query,omitempty"`
	// WebSearchCostPerRequest prices each server-side web search call (usage.tool_usage.web_search.num_requests).
	WebSearchCostPerRequest       *float64 `json:"web_search_cost_per_request,omitempty"`
	CodeInterpreterCostPerSession *float64 `json:"code_interpreter_cost_per_session,omitempty"`
	// InputCostPerQuery is the per-query rate rerank models bill on. Cohere and Bedrock both
	// define a query (a "search unit") as one query against up to 100 document chunks, so a
	// request over that many chunks bills as several queries. It is unrelated to
	// SearchContextCostPerQuery, which prices web-search context on chat models.
	//
	// Not applicable to Vertex: its Ranking API bills "ranking units" derived from record count
	// and title/content size, so a flat per-query rate would misprice every call.
	InputCostPerQuery        *float64 `json:"input_cost_per_query,omitempty"`
	InferenceGeoUSMultiplier *float64 `json:"inference_geo_us_multiplier,omitempty"`
	// CostPerRequest is a flat fee added once per billed request, on top of
	// whatever usage-based cost the request otherwise computes to.
	CostPerRequest *float64 `json:"cost_per_request,omitempty"`

	// Costs - OCR
	OCRCostPerPage        *float64 `json:"ocr_cost_per_page,omitempty"`
	AnnotationCostPerPage *float64 `json:"annotation_cost_per_page,omitempty"`

	// Costs - Time of day
	//
	// OffPeakCostMultiplier scales every usage-based charge when the request
	// falls outside the PeakHours windows. Every other rate on this struct is
	// the PEAK price, so the multiplier is expected to be in (0, 1] — 0.5 for
	// DeepSeek's 50% off-peak discount. Both fields must be present for a
	// discount to apply; either one alone bills at peak.
	OffPeakCostMultiplier *float64           `json:"off_peak_cost_multiplier,omitempty"`
	PeakHours             *PeakHoursSchedule `json:"peak_hours,omitempty"`
}

Options holds every individual cost field. Embedded into Entry and reused as the patch shape for Override.

type Override

type Override struct {
	ID            string                `json:"id"`
	Name          string                `json:"name"`
	ScopeKind     ScopeKind             `json:"scope_kind"`
	UserID        *string               `json:"user_id,omitempty"`
	VirtualKeyID  *string               `json:"virtual_key_id,omitempty"`
	ProviderID    *string               `json:"provider_id,omitempty"`
	ProviderKeyID *string               `json:"provider_key_id,omitempty"`
	MatchType     MatchType             `json:"match_type"`
	Pattern       string                `json:"pattern"`
	RequestTypes  []schemas.RequestType `json:"request_types,omitempty"`
	Options       Options               `json:"options"`
}

Override describes a scoped pricing override shared across config storage, model catalog compilation, and governance APIs.

func (*Override) IsValid

func (override *Override) IsValid() error

IsValid validates the shared override contract before persistence or runtime use.

type PeakHoursSchedule added in v1.7.0

type PeakHoursSchedule = configstoreTables.PeakHoursSchedule

PeakHoursSchedule and PeakHoursWindow are defined in the configstore tables package (TableModelPricing has to reference them and datasheet already imports tables, so the dependency can only run that way). Aliased here so the datasheet JSON shape reads as one self-contained type set.

type PeakHoursWindow added in v1.7.0

type PeakHoursWindow = configstoreTables.PeakHoursWindow

PeakHoursSchedule and PeakHoursWindow are defined in the configstore tables package (TableModelPricing has to reference them and datasheet already imports tables, so the dependency can only run that way). Aliased here so the datasheet JSON shape reads as one self-contained type set.

type ScopeKind

type ScopeKind string

ScopeKind identifies which governance scope an override applies to.

const (
	ScopeKindGlobal                ScopeKind = "global"
	ScopeKindProvider              ScopeKind = "provider"
	ScopeKindProviderKey           ScopeKind = "provider_key"
	ScopeKindVirtualKey            ScopeKind = "virtual_key"
	ScopeKindVirtualKeyProvider    ScopeKind = "virtual_key_provider"
	ScopeKindVirtualKeyProviderKey ScopeKind = "virtual_key_provider_key"
	// The user scope family targets the resolved calling user. It ranks
	// below the virtual-key family and above the provider scopes during
	// resolution: virtual-key pricing is checked first, then the user's.
	// Within the family, more identifiers = more specific.
	ScopeKindUser            ScopeKind = "user"
	ScopeKindUserProvider    ScopeKind = "user_provider"
	ScopeKindUserProviderKey ScopeKind = "user_provider_key"
)

type Store

type Store struct {
	// contains filtered or unexported fields
}

Store owns the pricing catalog (canonical rows, base-model index, derived datasheet view, supported request types/parameters) and pricing overrides.

All I/O is driven by the composer — Store does not own a ticker or the distributed lock; it exposes SyncFromURL / LoadFromDB / UpdateSyncConfig as the surface the composer calls.

func New

func New(configStore configstore.ConfigStore, logger schemas.Logger, cfg Config) *Store

New constructs a Store with the given config. The store is empty; callers (composer) drive bootstrap via LoadFromDB or LoadFromURLIntoMemory.

func NewTestStore

func NewTestStore(baseModelIndex map[string]string) *Store

NewTestStore constructs a minimal Store for unit tests without I/O. Optionally seed baseModelIndex so BaseModelName lookups resolve. A no-op logger is wired so cost / pricing paths (which assume Store.logger is non-nil) don't panic from external test code.

func (*Store) BaseModelName

func (s *Store) BaseModelName(model string) string

BaseModelName returns the canonical base model name. Uses the pre-computed base_model from the pricing catalog when present, falling back to algorithmic date/version stripping for unknown models.

"gpt-4o"               → "gpt-4o"
"openai/gpt-4o"        → "gpt-4o"
"gpt-4o-2024-08-06"    → "gpt-4o"  (algorithmic fallback)

func (*Store) CalculateBatchCostDetailsForUsage added in v1.6.0

func (s *Store) CalculateBatchCostDetailsForUsage(usage *schemas.BifrostLLMUsage, provider schemas.ModelProvider, model string, requestType schemas.RequestType, scopes *LookupScopes) BatchCostDetails

CalculateBatchCostDetailsForUsage computes batch result cost and returns the explicit batch rates used so aggregate logs can explain historical pricing. When the catalog has no batch-specific rate, it defaults to defaultBatchPricingRatio of the standard rate rather than refusing to price — only a model with no pricing at all (neither batch nor standard) is unpriced.

func (*Store) CalculateCacheEmbeddingCost added in v1.5.9

func (s *Store) CalculateCacheEmbeddingCost(cacheMetadata *schemas.BifrostCacheMetadata, scopes *LookupScopes) float64

CalculateCacheEmbeddingCost computes the semantic-cache embedding lookup cost.

func (*Store) CalculateCost

func (s *Store) CalculateCost(result *schemas.BifrostResponse, scopes *LookupScopes) float64

CalculateCost calculates the cost of a Bifrost response. It handles all request types, cache, guardrail, and routing billing, and tiered pricing. If scopes is nil, an empty LookupScopes is used; global and provider-scoped overrides may still apply since the provider is derived from the response.

func (*Store) CalculateCostBreakdown added in v1.6.0

func (s *Store) CalculateCostBreakdown(result *schemas.BifrostResponse, scopes *LookupScopes) *schemas.BifrostCost

CalculateCostBreakdown mirrors CalculateCost but returns the full per-category cost breakdown (input / output / cache) instead of only the total. Returns nil when there is no cost to record. CalculateCost is a thin wrapper over this that returns breakdown.TotalCost, so both paths compute cost identically.

func (*Store) CalculateCostBreakdownForUsage added in v1.6.0

func (s *Store) CalculateCostBreakdownForUsage(usage *schemas.BifrostLLMUsage, provider schemas.ModelProvider, model string, requestType schemas.RequestType, scopes *LookupScopes) *schemas.BifrostCost

CalculateCostBreakdownForUsage mirrors CalculateCostForUsage but returns the full per-category breakdown instead of only the total, so callers billing a bare usage object (failed/cancelled requests) can denormalize the input / output / additional split, not just the scalar total. Returns nil when there is no cost to record.

func (*Store) CalculateCostForUsage added in v1.3.22

func (s *Store) CalculateCostForUsage(usage *schemas.BifrostLLMUsage, provider schemas.ModelProvider, model string, requestType schemas.RequestType, scopes *LookupScopes) float64

CalculateCostForUsage computes the dollar cost from a bare usage object plus provider / model / request type, for cases where no full BifrostResponse exists. The primary use is billing partial usage carried on a failed or cancelled request via BifrostError.ExtraFields.BilledUsage: the provider consumed tokens, so we must charge for them even though there is no success response to read. It mirrors CalculateCost's compute path so success and failure billing use identical rates. Returns 0 when usage is nil. A thin wrapper over CalculateCostBreakdownForUsage returning only the total, so both paths compute cost identically.

func (*Store) CalculateGuardrailCost added in v1.5.9

func (s *Store) CalculateGuardrailCost(metadata *schemas.BifrostGuardrailMetadata, scopes *LookupScopes) float64

CalculateGuardrailCost computes judge cost when no parent response is available.

CalculateCost uses this for normal responses. Logging also calls it directly for input guardrail blocks, where the main provider call never produced a BifrostResponse.

func (*Store) CalculateVideoCostDetails added in v1.6.1

func (s *Store) CalculateVideoCostDetails(dims VideoPricingDimensions, provider schemas.ModelProvider, scopes *LookupScopes) VideoCostDetails

CalculateVideoCostDetails prices a video job from its merged dimensions.

Resolution order is provider-reported cost, then the catalog rate, then nothing. A provider that hands back an exact figure is always right about its own bill, so no estimate is allowed to override it, unless the provider is configured to ignore its reported cost.

func (*Store) CatalogPricingOverrides added in v1.5.9

func (s *Store) CatalogPricingOverrides(model string, provider schemas.ModelProvider, mode string) CatalogPricingOverrides

CatalogPricingOverrides returns the overrides relevant to one management catalog row. mode is the pricing mode of the base row being displayed ("chat", "embedding", …).

The management catalog has no virtual-key/user/selected-key context, so AppliedPatch is resolved with only the provider set — scopePriorityOrder then naturally yields [provider, global], the two scopes that hold for every caller. Overrides in the virtual-key/user/provider-key families are still returned in Matching so the UI can show them as informational.

func (*Store) DatasheetModelsForProvider

func (s *Store) DatasheetModelsForProvider(provider schemas.ModelProvider) []string

DatasheetModelsForProvider returns the per-provider model slice derived from pricing data on the last load/sync. Composer unions this with live.ModelsForProvider on read.

func (*Store) DatasheetProviders

func (s *Store) DatasheetProviders() []schemas.ModelProvider

DatasheetProviders returns every provider that has at least one pricing row in the datasheet view. Composer unions this with live + keyconfig to enumerate "all known providers" for GetProvidersForModel.

func (*Store) DeleteOverride

func (s *Store) DeleteOverride(id string)

DeleteOverride removes an override by ID.

func (*Store) DeprecatedDatasheetModelsForProvider added in v1.4.3

func (s *Store) DeprecatedDatasheetModelsForProvider(provider schemas.ModelProvider) []string

DeprecatedDatasheetModelsForProvider returns deprecated models from the datasheet view for provider. Deprecated models may disappear from provider list-models APIs but must remain visible in Bifrost catalog listings.

func (*Store) DistinctBaseModelNames

func (s *Store) DistinctBaseModelNames() []string

DistinctBaseModelNames returns every unique base name from the catalog. Used by governance for cross-provider model selection.

func (*Store) Get

Get returns the raw pricing row for (model, provider, requestType) or nil. Useful for callers that need exact pricing without override resolution.

func (*Store) GetCapabilityEntry

func (s *Store) GetCapabilityEntry(model string, provider schemas.ModelProvider) *Entry

GetCapabilityEntry returns capability metadata (context length, supported modes, etc.) for a (model, provider) pair. Prefers chat → responses → text-completion entries; falls back to the lexicographically first mode if none of the preferred modes match. Tries the exact model first, then the canonical base model.

func (*Store) GetModelParametersByModel

func (s *Store) GetModelParametersByModel(ctx context.Context, model string) (*configstoreTables.TableModelParameters, error)

GetModelParametersByModel reads a single model-parameter row from the DB. Used by the composer's cache-miss handler — installed via providerUtils.SetCacheMissHandler in the composer's Init.

func (*Store) GetPricingEntryForModel

func (s *Store) GetPricingEntryForModel(model string, provider schemas.ModelProvider) *Entry

GetPricingEntryForModel returns the first pricing entry found across known modes. Preserved for callers (inference handler) that want any pricing row for the model without specifying a request type.

func (*Store) GetSupportedParameters

func (s *Store) GetSupportedParameters(model string) []string

GetSupportedParameters returns the list of OpenAI-compatible parameter names a model accepts (e.g. temperature, top_p, tools). nil for unknown.

func (*Store) IsProviderCostIgnored added in v1.9.0

func (s *Store) IsProviderCostIgnored(provider schemas.ModelProvider) bool

IsProviderCostIgnored reports whether the provider's self-reported cost should be ignored.

func (*Store) IsRequestTypeSupported

func (s *Store) IsRequestTypeSupported(model string, requestType schemas.RequestType) bool

IsRequestTypeSupported checks whether a model declares support for the given request type via the model-parameters datasheet.

func (*Store) IsSameModel

func (s *Store) IsSameModel(model1, model2 string) bool

IsSameModel reports whether two model strings refer to the same underlying model after normalization.

func (*Store) IsTextCompletionSupported

func (s *Store) IsTextCompletionSupported(model string, provider schemas.ModelProvider) bool

IsTextCompletionSupported checks whether a model has a text_completion pricing entry — used by litellmcompat to decide whether to convert text completion requests into chat completion requests.

func (*Store) LastSyncedAt

func (s *Store) LastSyncedAt() time.Time

LastSyncedAt returns the last successful URL→DB sync timestamp; zero before any sync has completed.

func (*Store) LoadFromDB

func (s *Store) LoadFromDB(ctx context.Context) error

LoadFromDB reloads the in-memory pricing cache + datasheet view from the config store. Used by the composer at bootstrap and as the gossip ReloadFromDB handler on non-leader pods.

func (*Store) LoadFromURLIntoMemory

func (s *Store) LoadFromURLIntoMemory(ctx context.Context) error

LoadFromURLIntoMemory loads pricing from the URL directly into memory (no DB). Used when the composer was built without a config store.

func (*Store) LoadModelCapabilities added in v1.6.0

func (s *Store) LoadModelCapabilities(ctx context.Context, provider schemas.ModelProvider, model string) (*schemas.ModelCapabilities, error)

LoadModelCapabilities resolves a runtime (provider, model) pair to its capability record by reading datasheet rows out of the DB. It is the loader behind the provider-utils capability cache, and it owns the key translation that cache deliberately knows nothing about: the datasheet keys rows by a raw, often provider-prefixed name ("azure/gpt-5.1-chat") while callers ask by bare model plus provider.

A row only answers for a model when its own provider matches, so an OpenAI request for "o1" can never be served the "azure/o1" row.

Returns (nil, nil) when nothing matches — the cache remembers that, so an unknown model is looked up once rather than on every capability check. A store failure returns an error instead, which keeps the caller from caching "no capabilities" for a model whose row exists but was unreachable.

func (*Store) LoadModelParamsFromDB

func (s *Store) LoadModelParamsFromDB(ctx context.Context) (int, error)

LoadModelParamsFromDB bulk-loads model parameters from the DB into the provider-utils cache and the in-memory supportedResponseTypes / supportedParams indexes. Returns the row count so the composer can decide whether to background-sync from URL afterwards.

The provider-utils cache-miss handler in the composer still loads one row at a time when an unknown model is queried; both paths use the same JSON shape stored in the table.

func (*Store) LoadModelParamsFromURLIntoMemory

func (s *Store) LoadModelParamsFromURLIntoMemory(ctx context.Context) error

LoadModelParamsFromURLIntoMemory fetches model parameters from the URL and applies them in-memory only. Used when there's no config store.

func (*Store) LoadOverridesFromStore

func (s *Store) LoadOverridesFromStore(ctx context.Context) error

LoadOverridesFromStore reloads all overrides from the config store. Called at bootstrap and after force-reload paths.

func (*Store) MarkSynced

func (s *Store) MarkSynced(t time.Time)

MarkSynced records the timestamp of a successful sync — called by the composer's ticker after a successful tick.

func (*Store) ModelParametersURL

func (s *Store) ModelParametersURL() string

ModelParametersURL returns a snapshot of the model-parameters URL.

func (*Store) ReplaceIgnoreProviderCost added in v1.9.0

func (s *Store) ReplaceIgnoreProviderCost(providers []schemas.ModelProvider)

ReplaceIgnoreProviderCost resets the set of providers whose reported cost is ignored.

func (*Store) ResolveModelParameters added in v1.5.5

func (s *Store) ResolveModelParameters(ctx context.Context, model string) (*configstoreTables.TableModelParameters, error)

ResolveModelParameters looks up a model-parameter row for model, falling back through equivalent identifiers when the exact key has no row. The model-parameters datasheet keys models inconsistently — some bare ("gpt-5.5"), some provider-qualified ("openrouter/moonshotai/kimi-k2.5") — so clients querying with the IDs returned by /v1/models need this resolution to land on the stored key. Mirrors GetCapabilityEntry's exact → stripped → base-model fallback chain.

func (*Store) RoutingCallCost added in v1.6.1

func (s *Store) RoutingCallCost(call schemas.BifrostRoutingCall, scopes *LookupScopes) float64

RoutingCallCost calculates the cost of one routing-classification call — a semantic embed, LLM completion, or decision-model call. Exported so telemetry can price each call independently and unconditionally, while CalculateCost folds a call's cost into the request's cost only when that call's CountTowardBudgets is set. If scopes is nil, an empty LookupScopes is used.

func (*Store) SetIgnoreProviderCost added in v1.9.0

func (s *Store) SetIgnoreProviderCost(provider schemas.ModelProvider, ignore bool)

SetIgnoreProviderCost records whether a provider's self-reported usage.cost is ignored in favour of catalog pricing.

func (*Store) SetOnModelParametersApplied added in v1.6.0

func (s *Store) SetOnModelParametersApplied(fn func())

SetOnModelParametersApplied registers a callback fired after a model-parameters reload lands. The catalog uses it to drop capability records built from the previous sheet; keeping it a hook means the sync path does not need to know what caches exist above it.

func (*Store) SetOverrides

func (s *Store) SetOverrides(rows []configstoreTables.TablePricingOverride) error

SetOverrides replaces the full in-memory override set. Duplicate IDs in the input keep the last-seen entry (matching today's behavior).

func (*Store) SetPricingRowsForTest added in v1.8.1

func (s *Store) SetPricingRowsForTest(rows []configstoreTables.TableModelPricing)

SetPricingRowsForTest replaces pricingData with rows and rebuilds the derived indexes. Test-only seam for packages outside datasheet (e.g. the compat plugin) that need capability lookups such as max_output_tokens without running a sync.

func (*Store) SetSupportedParamsForTest added in v1.5.7

func (s *Store) SetSupportedParamsForTest(params map[string][]string)

SetSupportedParamsForTest replaces the supported-parameter index. Test-only seam for packages outside datasheet (e.g. the compat plugin) that need a catalog answering GetSupportedParameters without running a sync.

func (*Store) SyncFromURL

func (s *Store) SyncFromURL(ctx context.Context) error

SyncFromURL fetches the upstream pricing datasheet, persists it to the DB (when configStore != nil), and refreshes the in-memory cache + derived datasheet view. On URL failure it falls back to existing DB records when any exist, otherwise propagates the error.

The composer owns the distributed lock, the ticker, and the after-sync gossip hook — none of that lives here. SyncFromURL is the pure "URL → DB → memory" step.

func (*Store) SyncInterval

func (s *Store) SyncInterval() time.Duration

SyncInterval returns the minimum elapsed time between background syncs.

func (*Store) SyncModelParamsFromURL

func (s *Store) SyncModelParamsFromURL(ctx context.Context) error

SyncModelParamsFromURL fetches model parameters from the configured URL, persists to DB (when configStore != nil), and refreshes the in-memory indexes. On URL failure it falls back to DB records when any exist.

func (*Store) URL

func (s *Store) URL() string

URL returns a snapshot of the pricing URL.

func (*Store) UpdateSyncConfig

func (s *Store) UpdateSyncConfig(cfg Config)

UpdateSyncConfig replaces URL / params URL / interval atomically. The composer is responsible for triggering a fresh sync after this returns.

func (*Store) UpsertModelPricingAttributes

func (s *Store) UpsertModelPricingAttributes(ctx context.Context, model string, provider schemas.ModelProvider, attrs map[string]string) (int64, error)

UpsertModelPricingAttributes writes the additional_attributes column for every pricing row that matches (model, provider), then reloads the pricing cache so the new values are immediately visible to list-models. Returns the number of rows updated (0 = no such pricing row, which callers must surface as a validation error). An empty/nil attrs map clears the column.

func (*Store) UpsertOverrides

func (s *Store) UpsertOverrides(rows ...*configstoreTables.TablePricingOverride) error

UpsertOverrides inserts or replaces one or more overrides, rebuilding the lookup map exactly once at the end.

func (*Store) WriteGen added in v1.6.0

func (s *Store) WriteGen() uint64

WriteGen returns the monotonic count of membership rebuilds.

type VideoCostDetails added in v1.6.1

type VideoCostDetails struct {
	Cost             float64
	Priced           bool
	ProviderCostUsed bool
	Breakdown        *schemas.BifrostCost
}

VideoCostDetails is the outcome of pricing one video job. Priced separates "this costs nothing" from "no rate could be found", which the settlement engine treats as entirely different states.

type VideoPricingDimensions added in v1.6.1

type VideoPricingDimensions struct {
	// Model is the resolved model, which matters because several providers never
	// echo it back on retrieve.
	Model string `json:"model,omitempty"`
	// RequestType distinguishes generation from remix and edit. They can price on
	// different bases even for the same model.
	RequestType schemas.RequestType `json:"request_type,omitempty"`

	// Seconds and Size are the two dimensions every published video rate card uses
	// today. Size is "WxH"; the rate is banded on its short edge.
	Seconds *int   `json:"seconds,omitempty"`
	Size    string `json:"size,omitempty"`

	// Type is the operation selector ("upscale", "3d"). These do not bill per
	// second of output at all, so a rate keyed only on duration is wrong for them.
	Type *string `json:"type,omitempty"`
	// UpscaleFactor and TargetMegapixels are the upscale operation's own basis;
	// they are mutually exclusive.
	UpscaleFactor    *int `json:"upscale_factor,omitempty"`
	TargetMegapixels *int `json:"target_megapixels,omitempty"`

	// Audio: Veo publishes distinct with- and without-audio rates.
	Audio *bool `json:"audio,omitempty"`

	// OutputCount is how many clips the job actually returned. It is 0 until the
	// job produces them, and a job that has not finished still owes one clip's
	// worth rather than nothing — see computeVideoCost.
	OutputCount int `json:"output_count,omitempty"`

	// ProviderCost is the provider's own figure for the job, when it reports one
	// (Runware does). It wins over every rate below.
	ProviderCost *float64 `json:"provider_cost,omitempty"`

	// OutputTokens is the provider's metered output for the job (Seedance's
	// completion_tokens). Only the terminal response reports it.
	OutputTokens int `json:"output_tokens,omitempty"`

	// Extra carries request knobs that are not modeled above yet. It is the
	// research hatch: capture a provider-specific parameter here as soon as it
	// looks price-relevant, and promote it to a typed field once its rate is
	// actually published. Nothing reads it during pricing today.
	Extra map[string]any `json:"extra,omitempty"`
}

VideoPricingDimensions is every factor that can move the price of one video job.

It exists because a video is billed long after it is asked for. The request says what was ordered, the terminal response says what was actually produced, and neither alone is enough: most providers' retrieve response reports little beyond "done", so a dimension not captured at submission is usually unrecoverable by the time settlement runs.

Adding a dimension is deliberately cheap. The whole struct is persisted as JSON in a single TEXT column, so a new field needs no migration and no capture-side change beyond setting it — which is the point: the provider rate cards are not fully mapped yet, and a factor worth capturing should not have to wait for one. Fields that no rate reads yet are still worth recording.

func VideoDimensionsFromResponse added in v1.6.1

func VideoDimensionsFromResponse(resp *schemas.BifrostVideoGenerationResponse) VideoPricingDimensions

VideoDimensionsFromResponse reads back everything a terminal video response actually reports. What that amounts to varies enormously by provider — OpenAI echoes model, duration and size; several others report only a status and a URL — which is precisely why the submitted request is kept as the fallback.

func (VideoPricingDimensions) MergedWith added in v1.6.1

MergedWith layers observed dimensions over captured ones: whatever the argument actually knows wins, and the receiver fills every remaining gap.

The direction matters at settlement. The request is a statement of intent — a provider may clamp a duration, downscale a resolution, or return fewer clips than asked for — so the terminal response is authoritative wherever it says anything at all, and the submitted request is the fallback for everything it stays silent about (which, for most providers, is nearly everything).

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL