You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
I'd like to add a built-in, config-declared quota probe for API-key credentials such as openai-compatibility relays, so the balance of a custom endpoint can be queried and shown without writing a native plugin. I'm happy to send the PR, but quota_probe was deliberately hidden from v8 in #6547, so I'd like to confirm the direction first.
Current behavior
Checked against main at 7cad1cb (v8.0.51). There is no way to do this today without a plugin:
Mechanism
Works for config API keys on v8?
Why
QuotaProvider plugin
Backend yes, panel no
QuotaFetchRequest receives base_url and api_key through Attributes, but it needs a compiled native plugin per setup, and the credential never reaches the panel (see below).
Declarative quota_probe
No
It is read from auth.Metadata, which config-synthesized credentials can't set, and it only runs on the v0 /quota/fetch route (auth_files.go:699). The panel only talks to v8.
Quota page in the management center
No
It is driven by the auth-files list, and buildAuthFileEntryLocked returns nil for credentials with no path that aren't runtime_only. That covers every config API key.
claude-api-key entries have a second problem: their provider is claude, so the panel treats them as the built-in OAuth quota card (BUILT_IN_QUOTA_PROVIDERS), and they couldn't use a generic plugin card even if listed.
Use case
Many relays and third-party gateways expose a simple balance endpoint, for example GET /v1/balance or GET /dashboard/billing/credit_grants, which returns JSON containing the remaining balance. Users currently have to check each vendor's dashboard, or use a separate tool. cc-switch solves the same problem with a per-provider usage script, which is essentially "one HTTP request plus one extractor".
Those endpoints report an absolute balance, not a fraction, so the result belongs in QuotaFetchResponse.Summary (format: currency), not Groups[].Buckets.
Proposal
Reuse the existing probe rather than adding a script runtime.executeQuotaProbe and mapProbeResponse already do the request and the gjson mapping. I'd rather not embed a JS engine: it adds a dependency and turns anyone with config write access into a script author, while nearly every balance API fits a declarative mapping. Anything more unusual can stay a QuotaProvider plugin.
Proposed config (same shape on claude-api-key, codex-api-key and gemini-api-key, if you want that coverage in the first PR):
Config: add quota-probe to the provider types, copy it into Metadata["quota_probe"] in the synthesizer, and include it in config diffing and the management config output.
Mapping: add path-based summary mapping to mapProbeResponse. Today it maps plan, tier and groups, but summary is only passed through from a top-level upstream field in the normalized shape, so an arbitrary balance field can't be mapped.
Discovery: expose supports_quota_probe on the v8 provider config entries, keyed by auth_index, so config credentials don't need to be added to the auth-files list. This keeps auth-files semantics unchanged.
Panel (separate PR): show the balance on the provider workbench rows, which already have authIndex.
Docs and tests: cover config.example.yaml, docs/management-api-v8.md, the v8 route test, and the mapping cases.
Hardening
Moving the probe from a v0 fallback to a config feature is a good moment to tighten it:
Add a request timeout, since the http.Client currently has none and relies on the request context alone.
Cap the response body with io.LimitReader, in place of the unbounded io.ReadAll.
Redact $TOKEN$ values from errors. A token placed in the URL can surface through probe request failed: %w, because *url.Error includes the URL, and non-2xx bodies are echoed back to the caller.
Refuse redirects to another host while a token is attached.
Let the panel fetch on demand or with a cached TTL, with no background polling by default.
Out of scope
Automatic routing decisions based on the balance, such as disabling a credential at zero. That can follow later, once the data is available.
Questions for maintainers
Was hiding quota_probe from v8 in Fix v8 plugin quota contract gaps #6547 a decision to retire declarative probes, or only a way to close the contract gap? If it's being retired, would you prefer a different design, such as an official generic quota plugin that reads this config?
Is POST /v8/management/credentials/quota/probe acceptable, given that server_management_v8_test.go currently asserts credentials/quota/* returns 404 on v8?
Should config credentials stay out of auth-files, as proposed in step 4, or would you rather list them there?
If the direction looks right, I'll open the backend PR against dev and follow with the panel PR.
This discussion was converted from issue #7189 on November 02, 2026 23:23.
Heading
Bold
Italic
Quote
Code
Link
Numbered list
Unordered list
Task list
Attach files
Mention
Reference
Menu
reacted with thumbs up emoji reacted with thumbs down emoji reacted with laugh emoji reacted with hooray emoji reacted with confused emoji reacted with heart emoji reacted with rocket emoji reacted with eyes emoji
Uh oh!
There was an error while loading. Please reload this page.
Summary
I'd like to add a built-in, config-declared quota probe for API-key credentials such as
openai-compatibilityrelays, so the balance of a custom endpoint can be queried and shown without writing a native plugin. I'm happy to send the PR, butquota_probewas deliberately hidden from v8 in #6547, so I'd like to confirm the direction first.Current behavior
Checked against
mainat7cad1cb(v8.0.51). There is no way to do this today without a plugin:QuotaProviderpluginQuotaFetchRequestreceivesbase_urlandapi_keythroughAttributes, but it needs a compiled native plugin per setup, and the credential never reaches the panel (see below).quota_probeauth.Metadata, which config-synthesized credentials can't set, and it only runs on the v0/quota/fetchroute (auth_files.go:699). The panel only talks to v8.buildAuthFileEntryLockedreturnsnilfor credentials with nopaththat aren'truntime_only. That covers every config API key.claude-api-keyentries have a second problem: their provider isclaude, so the panel treats them as the built-in OAuth quota card (BUILT_IN_QUOTA_PROVIDERS), and they couldn't use a generic plugin card even if listed.Use case
Many relays and third-party gateways expose a simple balance endpoint, for example
GET /v1/balanceorGET /dashboard/billing/credit_grants, which returns JSON containing the remaining balance. Users currently have to check each vendor's dashboard, or use a separate tool. cc-switch solves the same problem with a per-provider usage script, which is essentially "one HTTP request plus one extractor".Those endpoints report an absolute balance, not a fraction, so the result belongs in
QuotaFetchResponse.Summary(format: currency), notGroups[].Buckets.Proposal
Reuse the existing probe rather than adding a script runtime.
executeQuotaProbeandmapProbeResponsealready do the request and the gjson mapping. I'd rather not embed a JS engine: it adds a dependency and turns anyone with config write access into a script author, while nearly every balance API fits a declarative mapping. Anything more unusual can stay aQuotaProviderplugin.Proposed config (same shape on
claude-api-key,codex-api-keyandgemini-api-key, if you want that coverage in the first PR):Suggested PR scope
quota-probeto the provider types, copy it intoMetadata["quota_probe"]in the synthesizer, and include it in config diffing and the management config output.summarymapping tomapProbeResponse. Today it maps plan, tier and groups, butsummaryis only passed through from a top-level upstream field in the normalized shape, so an arbitrary balance field can't be mapped.POST /v8/management/credentials/quota/probewith{"auth_index": "..."}, the option suggested in v8 management: plugin quota contract gaps (quota plugin lookup & quota_probe) #6519. It would only run a probe when one is declared in config.supports_quota_probeon the v8 provider config entries, keyed byauth_index, so config credentials don't need to be added to the auth-files list. This keeps auth-files semantics unchanged.authIndex.config.example.yaml,docs/management-api-v8.md, the v8 route test, and the mapping cases.Hardening
Moving the probe from a v0 fallback to a config feature is a good moment to tighten it:
http.Clientcurrently has none and relies on the request context alone.io.LimitReader, in place of the unboundedio.ReadAll.$TOKEN$values from errors. A token placed in the URL can surface throughprobe request failed: %w, because*url.Errorincludes the URL, and non-2xx bodies are echoed back to the caller.Out of scope
Automatic routing decisions based on the balance, such as disabling a credential at zero. That can follow later, once the data is available.
Questions for maintainers
quota_probefrom v8 in Fix v8 plugin quota contract gaps #6547 a decision to retire declarative probes, or only a way to close the contract gap? If it's being retired, would you prefer a different design, such as an official generic quota plugin that reads this config?POST /v8/management/credentials/quota/probeacceptable, given thatserver_management_v8_test.gocurrently assertscredentials/quota/*returns 404 on v8?If the direction looks right, I'll open the backend PR against
devand follow with the panel PR.All reactions