Skip to content

[FEATURE]: Document free-tier accounting for free models (idle time, pool scope, remaining allowance) #57540

Closed as not planned

Description

@tomsun66-star

Feature hasn't been suggested before.

  • I have verified this feature I'm about to request hasn't been suggested before.

Describe the enhancement you want to request

  • I have verified this feature I'm about to request hasn't been suggested before.

Describe the enhancement you want to request

The free models (opencode/minimax-m3.1-flash-free and the other *-free models) have no documented accounting rules. Four things a user cannot answer from the docs today:

1. The Go pricing table says "Unlimited" — but a daily cap exists.
OpenCode Go lists MiniMax M3.1 Flash Free as Free / Unlimited (monthly limit) and Unlimited / Unlimited / Unlimited in the "Estimated requests" table. In reality the free models are subject to a daily request cap that surfaces as FreeUsageLimitError (HTTP 429, retry-after up to 24 h — PR #49195), reported as roughly 200 requests/day (#33495, #33318, #15585). The docs never mention this cap, so "Unlimited" reads as "no limit". This row is the one statement I found that is simply wrong.

2. Is idle wall-clock time charged?
Nothing in the docs says. The limiter decrements per request, so an open idle session appears to consume nothing — but "5-hour — 20% of the monthly limit" is easy to read as a wall-clock window that runs while the session is open. Please state explicitly that free-tier accounting is exclusively request/token based and that idle time is not charged.

3. Pool scope: does switching models give me a fresh allowance?
The behaviour differs per regime and is not documented:

  • Free models (in-app): the bucket is per IP / per API key and decremented per request on a daily cycle (packages/console/app/src/routes/zen/util/handler.ts: createIpRateLimiter(modelInfo.id, modelInfo.rateLimit, ip, ...), createKeyRateLimiter(...), createTrialLimiter(modelInfo.trialProvider, ip)). Reports indicate that all free models fail at the same time once it is exhausted, i.e. the bucket appears shared across free models — switching from minimax-m3.1-flash-free to glm-5.3-flash-free would not reset it. Please confirm.
  • Free models vs. Zen balance: separate pools. A maintainer comment on #45324 states free models "never use your Zen balance, so adding credits doesn't lift that limit". The paid balance itself is shared across models (BillingTable.balance, monthlyUsage per workspace and per user). Please document that the two are independent.
  • Go plan: "Each model has the following usage limits: 5-hour — 20% … weekly — 50% … monthly — 100%" implies a per-model meter, so switching models switches meters. Please confirm.

4. There is no way to see the remaining allowance.
Go subscribers can "track your current usage in the console". For the in-app free models (no console account) there is no documented place to look; the error body is only "Rate limit exceeded. Please try again later." with a retry-after header. Please expose the remaining allowance (e.g. a response header and/or a console page) and document it.

What I would like to see

A "Free model limits" section in the docs that states, unambiguously:

  1. free-tier accounting is exclusively request/token based and idle time is not charged;
  2. the size of the daily allowance, what it meters, its reset window, and its scope (IP / API key / account / workspace);
  3. whether that allowance is shared across free models and independent of the paid Zen balance;
  4. how Go's per-model limits relate to a shared pool, if any;
  5. where the user can see the remaining allowance.

Notes

  • This is about documentation and visibility, not about changing the cap. The cap behaviour is already tracked in #33318, #33495, #45324, #43208 and #42938.
  • Environment: OpenCode app, model opencode/minimax-m3.1-flash-free, Linux. Observed: no requests are issued while a session is idle; after the allowance is exhausted, all free models fail simultaneously with FreeUsageLimitError / HTTP 429, and adding Zen credit does not change that.

Activity

  1. opencode-agent commented on Oct 20, 2026

    @opencode-agent
    Contributor

    Thanks for laying this out so clearly, @tomsun66-star!

    As I understand it, you'd like a "Free model limits" section in the docs that covers:

    • how free-model usage is counted (per request/token, with idle time not charged)
    • the size of the daily allowance, when it resets and what it is tied to (IP, API key or account)
    • whether it is shared across all free models and separate from your Zen balance
    • how Go's per-model limits relate to it
    • where you can see how much allowance is left

    You'd also like the "Unlimited" entry for free models in the Go table corrected, since a daily cap does apply. The maintainers will consider it.

    Related issues, starting with the closest:

  2. Eriol1805 commented on Oct 25, 2026

    @Eriol1805

    Independent reproduction: rejection is per-request, not account-global (Windows + TUI + mimo-v2.6-flash-free)

    Data point for the "pool scope" question above, reproduced from a different OS/client/model than #58751.

    Same account, same machine, same minutes of 2026-10-25 (UTC), both sessions verified identical via session.list — providerID: opencode, model: mimo-v2.6-flash-free, variant: default:

    session assembled context (GET /api/session/{id}/context) result
    ses_09d7744… (older, long-running) 809,672 bytes Free usage limit reached on every prompt
    ses_e8aaa88… (new session) 310,745 bytes normal answers, in parallel

    Server log (~/.local/share/opencode/log/opencode.log) — every QuotaExceeded belongs to the old session only, including attempts made while the new session was receiving successful responses:

    2026-10-25T16:28:48Z AI.Error.QuotaExceeded: Free usage limit reached … sessionID=ses_09d7744…
    2026-10-25T23:06:52Z AI.Error.QuotaExceeded: Free usage limit reached … sessionID=ses_09d7744…
    2026-10-25T23:10:05Z AI.Error.QuotaExceeded: Free usage limit reached … sessionID=ses_09d7744…
    2026-10-25T23:14:26Z AI.Error.QuotaExceeded: Free usage limit reached … sessionID=ses_09d7744…
    

    No QuotaExceeded entry exists for any other session in the log. The client-facing message in the blocked session is: You've used today's free messages. They reset at 21:00.

    Environment: Windows 11, opencode v2.1.4, TUI, opencode/mimo-v2.6-flash-free, free tier only (no Go, $0 balance).

    This matches the request-size behavior in #58751 (closed as billing): the account was not blocked — a ~2.6× larger assembled context was rejected in the same minutes a small request succeeded. Suggested answer for the documentation requested here: rejection is not applied account-wide; it tracks the individual request (context size), so a new session keeps working while the old one stays refused, and the error text ("today's free messages") misdescribes what is actually being measured.

  3. Eriol1805 commented on Oct 27, 2026

    @Eriol1805

    Follow-up: reproduced upstream — the discriminator is the session ID header, and Go's endpoint accepts all session IDs

    Continuing my previous comment. Environment: v2.1.4, Windows (a second machine on Fedora does not reproduce this — same account).

    1. A/B matrix at the CLI level (interleaved, same minute, same model)

    Session How the ID was obtained session.generate result
    ses_e85491603ffeaKOUQZjBbdWTN1 server-generated (session.create without id) 200 OK
    ses_abcdef0123456789abcd session.create with custom id (20 hex) 503 FreeUsageLimitError
    ses_e8aaa88caffeTEST1234abcd session.create with custom id (server-like format) 503
    ses_e8aaa88caffeAAAAbcdefGHIj session.create with custom id (26 chars: 12 hex + 14 base62) 503
    any session/import session custom info.id 503

    Key points:

    • The gate fires with 0 messages (freshly created sessions) — it is not about context size, tokens, cost, time.*, agent, model, outcome, title, projectID, message content, or the account's global quota (the server-generated ID succeeds interleaved with failures at the same instant).
    • The DB rows differ only in id and slug (random title slug).
    • ID format is irrelevant: both 20-hex and server-style 26-char IDs are accepted by POST /api/session and then blocked at generate time.
    • session.fork / session.compact don't hit the gate (they never reach generate).
    • Daily reset does not clear it: the original session was still returning 503 at 23:00 after a 21:00 reset.

    2. Captured the real upstream request (local MITM proxy)

    I pointed the opencode provider at a local forwarding proxy and let the CLI issue its real request. Both bodies are byte-identical (57,301 B); the only difference between a working and a failing request is:

    x-opencode-session: <session id>
    x-opencode-session-id: <session id>
    x-session-affinity: <session id>
    x-session-id: <session id>
    

    Full real headers also include Authorization: Bearer st_… (a session token — not the oc_sk_ from auth.json nor the account sk- key), x-opencode-org-id: wrk_…, x-opencode-project, x-opencode-client: cli, b3, traceparent.

    Replaying those exact requests (real token + org + all headers) directly against https://opencode.ai/inference/openai/v1/chat/completions returns:

    429 {"type":"error","error":{"type":"FreeUsageLimitError","message":"Free usage limit reached. …"}}
    Retry-After: 84692
    

    for all three variants (no session headers, custom session id, server-generated session id). Note this replay was captured after the free daily allowance had been exhausted during my testing; the 200-vs-503 discrimination by session id was observed at CLI level before exhaustion, with the byte-identical payloads/headers shown above (also captured with a local echo provider).

    3. What OpenCode Go changes

    The Go endpoint https://opencode.ai/inference/go/openai/v1 behaves differently:

    • Without session headers: 400 MissingSessionID — "Request is missing x-opencode-session and cannot be routed efficiently" (so upstream routes by session id there).
    • With x-opencode-session + real token: 200 OK for BOTH a server-generated and a custom/imported session id.

    After activating OpenCode Go, the CLI with provider opencode-go generates successfully for both ID types (session.generate on a custom-ID session returns {"data":{"text":"OK"}}), while the free provider (/inference/openai/v1) still returns 429 FreeUsageLimitError for everything (free daily allowance exhausted during this testing — Retry-After ≈ 23.5 h, i.e. it expires ~00:00 UTC).

    Working hypothesis

    On the free endpoint, quota/routing appears to be keyed per x-opencode-session*: sessions whose id was not produced by the server's own session.create path land in a bucket that always answers FreeUsageLimitError, regardless of the account's actual state. The Go endpoint has no such discrimination.

    Questions for maintainers:

    1. Is the free-endpoint quota ledger keyed by session id, and are client-provided (session.create with id, session/import) ids registered differently from server-generated ones?
    2. If so, is that intentional? As it stands, experimental.session.import and custom session ids are unusable on the free tier.

    Happy to run more experiments — I have the exact upstream requests (bodies + headers) captured for both cases.

    Timestamps for reference (UTC): 2026-10-25 16:28, 23:06, 23:10, 23:14; 2026-10-26 02:19–02:27.

  4. neriousy commented on Nov 12, 2026

    @neriousy
    Member

    Free-tier questions and problems go in #59387. Closing this one in favor of that thread.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions