Summary
OpenAI OAuth accounts can still be shown as Plus in the admin account list even after the upstream OpenAI response reports plan_type: "free" in a usage_limit_reached 429 response.
This makes the displayed account plan stale/misleading: the account is handled by OpenAI as a Free account, while Sub2API still displays the cached credentials.plan_type as Plus.
Environment
- Repository:
Wei-Shaw/sub2api
- Observed on commit:
dbc8ae6
- Deployment: Docker image built from current
main
- Account type: OpenAI OAuth
Reproduction
- Have an OpenAI OAuth account whose local
accounts.credentials.plan_type is cached as plus.
- Run the admin account test endpoint, for example
POST /api/v1/admin/accounts/{id}/test, after the upstream account has become Free or has exhausted its Free usage window.
- OpenAI returns a 429 response like:
{
"error": {
"type": "usage_limit_reached",
"message": "The usage limit has been reached",
"plan_type": "free",
"resets_at": 1778634027,
"resets_in_seconds": 210842
}
}
- The account list still shows
Plus.
Actual Behavior
rate_limit_reset_at is updated correctly from resets_at.
credentials.plan_type remains the previous cached value, for example plus.
- The frontend
PlatformTypeBadge renders this stale planType and continues to show Plus.
Expected Behavior
When OpenAI returns error.plan_type in usage_limit_reached / related 429 responses, Sub2API should either:
- persist the observed value to
accounts.credentials.plan_type, especially when it changes to free; or
- store/display an explicit observed/current plan state so the UI does not present stale cached subscription data as current.
Also, if subscription_expires_at is already in the past, the admin UI should probably avoid showing the cached Plus badge as a current plan unless a fresh account check confirms it.
Suspected Code Paths
backend/internal/service/account_test_service.go
reconcileOpenAI429State parses/reset-limits the 429 state but does not update plan_type.
backend/internal/service/ratelimit_service.go
parseOpenAIRateLimitResetTime parses reset fields from usage_limit_reached, but ignores error.plan_type.
frontend/src/components/common/PlatformTypeBadge.vue
- renders
planType directly from cached account credentials without checking staleness or expiry.
Notes
This is visible when an account was previously cached as Plus but OpenAI now responds with plan_type: "free". In that state, routing/limits should follow the upstream response, while the admin UI still suggests the account is Plus.
Summary
OpenAI OAuth accounts can still be shown as
Plusin the admin account list even after the upstream OpenAI response reportsplan_type: "free"in ausage_limit_reached429 response.This makes the displayed account plan stale/misleading: the account is handled by OpenAI as a Free account, while Sub2API still displays the cached
credentials.plan_typeas Plus.Environment
Wei-Shaw/sub2apidbc8ae6mainReproduction
accounts.credentials.plan_typeis cached asplus.POST /api/v1/admin/accounts/{id}/test, after the upstream account has become Free or has exhausted its Free usage window.{ "error": { "type": "usage_limit_reached", "message": "The usage limit has been reached", "plan_type": "free", "resets_at": 1778634027, "resets_in_seconds": 210842 } }Plus.Actual Behavior
rate_limit_reset_atis updated correctly fromresets_at.credentials.plan_typeremains the previous cached value, for exampleplus.PlatformTypeBadgerenders this staleplanTypeand continues to showPlus.Expected Behavior
When OpenAI returns
error.plan_typeinusage_limit_reached/ related 429 responses, Sub2API should either:accounts.credentials.plan_type, especially when it changes tofree; orAlso, if
subscription_expires_atis already in the past, the admin UI should probably avoid showing the cachedPlusbadge as a current plan unless a fresh account check confirms it.Suspected Code Paths
backend/internal/service/account_test_service.goreconcileOpenAI429Stateparses/reset-limits the 429 state but does not updateplan_type.backend/internal/service/ratelimit_service.goparseOpenAIRateLimitResetTimeparses reset fields fromusage_limit_reached, but ignoreserror.plan_type.frontend/src/components/common/PlatformTypeBadge.vueplanTypedirectly from cached account credentials without checking staleness or expiry.Notes
This is visible when an account was previously cached as Plus but OpenAI now responds with
plan_type: "free". In that state, routing/limits should follow the upstream response, while the admin UI still suggests the account is Plus.