Releases: is7Qin/c3api
Release list
v0.0.1-beta.5
[v0.0.1-beta.5] - 2026-08-26
Breaking
-
Changing or resetting a password now revokes all of that user's existing JWTs immediately (they get 401 and must log in again) (spec 2026-08-25-jwt-password-revocation-design); previously tokens stayed valid until their natural ≤24 h expiry. Mechanism:
usersgains atoken_versioncolumn (default 0; column ADD auto-applies at startup migrate),Issuestamps the current version into the JWT (verclaim) at login/registration, and both/api/user(RequireJWT) and the admin JWT path (adminAuth) reject when the in-memory user snapshot's version differs from the claim — no Redis denylist, no refresh tokens. Both password-write paths (self-service change-password and email-code reset) bump the version in a single atomic statement plus the standard invalidate+NOTIFY pair. Existing tokens keep working after upgrade until each user's first password change (missingverdecodes to 0 = DB default). -
Email verification codes moved from PostgreSQL to Redis (spec 2026-08-25-emailcode-redis-migration): the
email_codestable is removed (internal/entregenerated; a leftoveremail_codestable on an old database is dead data — not dropped, not migrated, per the fresh-setup policy). Codes now live as one Redis HASH per(purpose, email)underc3api:emailcode:<purpose>:<email>(internal/verification, injected viaService.SetEmailCodeStore) with a native TTL replacing theexpires_atcolumn, so an expired code now answers "code invalid" instead of "code expired" (same 400 sentinel class, handler contract unchanged). Unconsumed codes do not survive a restart (compose Redis keepsappendonly = no) — users simply request a new code; resend rate limiting (60 s window) is preserved via theupdated_atfield. -
Redis is now a required dependency alongside PostgreSQL (spec 2026-08-25-redis-foundation-design):
[redis].addris mandatory (empty = startup fatal; unreachable at startup = fatal via a fail-fast Ping), withC3API_REDIS_ADDR/C3API_REDIS_PASSWORD/C3API_REDIS_DBenv overrides and placeholder-password rejection reusing the existing secret check. Upgrading = provisioning a Redis instance (redis:8-alpinejoins the compose stack as a required service). Redis carries only discardable ephemeral coordination state — never a cache layer or system of record. -
The manual
cluster.instancessetting is removed (spec 2026-08-25-redis-instance-discovery-design): multi-instance budget sharing now auto-discovers the live instance count via Redis ZSET heartbeats (internal/discovery, 1 s tick / 15 s member TTL with clock-skew margin, graceful-stop ZREM for immediate scale-down; on Redis outage the last count freezes instead of failing closed). The admin Settings "Cluster" tab is gone. -
InputTokens ledger semantics changed for OpenAI-family upstreams (chat / responses REST+WS / codex):
usage_logs.input_tokensnow carries billable input (wire value =input_tokens − cached_tokens; OpenAI semantics have cached ⊆ input — previously the cached-hit portion was double-billed at both the input rate and the cache-read rate); cached reads continue to be billed separately viacache_read_tokens× CacheReadPerM. Historical rows keep the old semantics (no backfill — Beta has no migration path);total_tokensis numerically unchanged everywhere (quota deduction and stats totals unaffected); Anthropic upstreams are unaffected (theircache_readis not part ofinput_tokensto begin with). -
Billing ledger rewritten as a cursor consumer over
usage_logs: the in-memory billing queue (which lost 4.16 M ledger rows under load — usage logs survived, deductions never happened) is gone.usage_logsgains abilledboolean with a partial index (WHERE NOT billed) that acts as the durable cursor; the usage flusher is now the table's single writer and stamps each row's fate at birth (billing.capture=falseor anonymous requests are born absorbed). The billing worker sweeps unbilled rows in batches (session-scoped advisory lock, per-user grouped concurrent deduction, poison-row quarantine, zero-cost bulk marking) with crash-safe exactly-once semantics: deduction + marking commit atomically, uncommitted work replays. Settlement executes as per-lane set-based SQL statements (balance lane / FEFO temp lane / zero-cost sweep) instead of per-user round trips; measured sustained drain 11 k+ ledger rows/s on the reference storm (vs ~100 rows/s for the retired in-memory queue path), and the legacy per-group deduction surface has been fully retired. -
Ops overview alerts contract changed: the billing trio is now
billing_lag_ms/billing_unbilled_rows/billing_quarantined_rows(replacingbilling_pending_waterline_ms, which measured an in-memory queue depth that no longer exists). -
Pricing storage unified: the three price tables (
pricing/image_prices/function_prices) are retired in favor of the two-tableprice_entries+price_variantsmodel; the admin endpointsGET /api/admin/pricing,GET /api/admin/image-prices,GET /api/admin/function-pricesand theirPUT/DELETEcounterparts are removed and consolidated intoGET /api/admin/prices(list filterspage/page_size/mode/source/model/sort/order) plusGET|PUT|DELETE /api/admin/prices/entry?model=andGET|PUT|DELETE /api/admin/prices/variants?model=(modelas a required query parameter);POST /pricing/syncstays and gains a preview counterpartPOST /pricing/sync/preview;above_thresholdtiered-pricing semantics are retired in favor of whole-entry variant switching (first match wins); resp-detect requests carrying images but no per-image price now bill as pure tokens (previously the whole request zeroed out asno_price). -
Stats API redesigned around bounded query shapes:
GET /api/admin/stats(unbounded raw-bucket dump) is removed and replaced byGET /api/admin/stats/trend(time series),/stats/top(entity leaderboard),/stats/entity-trend(single-entity drill-down) and/stats/ttft(TTFT percentile card — histogram sketch platform-wide, exactpercentile_contwhen entity-filtered)./api/user/statsnow returns aggregated trend points instead of raw buckets (optionalmodelfilter gained), and/api/user/stats/ttftis new. Storage:usage_statsslimmed to hour × group × model keys (account/template/user/is_error dimensions dropped,is_errordemoted to a column) and a new daily-partitionedusage_entity_statshourly rollup now backs every entity-scoped view.
Added
-
Cross-instance concurrency consensus for all three limit layers (user / API-key / upstream account): each instance admits against a local share
max(1, floor(limit/N))with N auto-discovered via Redis heartbeats; overflow borrows through a cluster view built from per-instance in-flight reports reconciled every 500 ms into a locally-cached snapshot. The request path stays 100 % local memory — zero Redis round-trips, and N=1 short-circuits mathematically (no code path touches Redis at all). Redis outages degrade fail-open to pre-consensus per-instance behavior instead of rejecting traffic; measured drift under 2× oversubscription stays within the documented N × tick-window bound. -
Ops observability for the two concurrency-view sync workers (
conc-sync/account-conc-sync:last_tick_ok/consecutive_errors/tracked_entries) — the fail-open degradation above is silent by design, so these counters are its only visible trace; worker cards on the ops page now show localized display names. -
Redis infrastructure (foundation for the required-dependency model):
pkg/redisxis the repo-wide sole client construction point (Open= construct + fail-fast Ping; no command-level wrapper by design),internal/configgains a strictly-validated[redis]section, andinternal/discoveryimplements ZSET-membership instance discovery wired as a worker (registered between billFlusher and listener/authSync so graceful shutdown removes the member before the final billing-cursor sweep, observable viaGET /api/admin/ops/workersasinstances/last_tick_ok/consecutive_errors). Future consumers on the roadmap: JWT revocation denylist, email verification codes (both since shipped — see above). -
Billing lag observability: worker stats expose cursor lag (
LagMs/UnbilledRows/QuarantinedRows/LastCycleUnixMs) with a guardrail warning when unpaid backlog exceeds 80 % of the retention window; benchmark-adapted deduction path sustains ~50 k ledger rows/s (mark step ~98 k rows/s measured). -
Email service: registration email verification codes and password reset by emailed code — SMTP relay configured through runtime settings (
mail.*, admin console → Settings → Mail tab, disabled by default; TLS defaults to implicit SMTPS on port 465), editable email templates with built-in English defaults and fallback. Delivery runs on a dedicated background mail worker (bounded queue, 3-attempt retry with backoff) observable via/api/admin/ops/workers. -
Admin settings page reorganized into category tabs (signup / defaults / pricing sync / tier policy / cluster / mail), with a two-column Mail tab covering SMTP config and template editors; new user-facing pages for register code entry and forgot-password flow.
-
Loadtest tooling for full-surface hammering:
-mode api-admin(26 weighted scenarios incl. the new stats shapes, redemption codes handed off via-codes-out) and-mode api-user(JWT pool + code redemption,-codes-in),-format images,-api-reads-only, plus a fake-upstream images endpoint.
Changed
- Statistics reads are fully pushed down to PostgreSQL: dashboards aggregate via
GROUP BY date_truncserver-side instead of pulling whole dimension cubes into gateway memory (the old endpoint materialized up to millions of rows per call and was OOM-killed at 33.6 GB under load). Measured on the high-cardinality stress dataset: stats endpoints 24–27 s average → hundreds of ms, ...
v0.0.1-beta.4
[v0.0.1-beta.4] - 2026-08-22
Breaking
- All management/user APIs moved under the
/apiprefix (/api/admin/*,/api/user/*); the SPA is now served from the repository root. The data plane (/v1/*) is unchanged — scripts and integrations must update their base URLs.
Added
- Rules engine composite conditions:
http_status_in/model_in/error_message_contains_inOR-match sets (mutually exclusive with the single-value forms, empty arrays rejected with 400), precompiled into zero-allocation lookup sets on the hot path; a fully-emptythen {}is now a valid pure-passthrough rule. - Rules-driven response shaping:
response_code/custom_messageare pointer-intent (nil passes the upstream code/message through), the event taxonomy isok/429/4xx/5xx/network, unmatched non-ok errors normalize to a fixed 502 message, user-face error logs are sanitized accordingly, andwhen.modelmatches the final mapped model uniformly across the REST/WS/log surfaces. - Batch cooldown reset: new
POST /accounts/batch-reset-cooldown; writingstatus=activethrough PUT/batch-update also clears the cooldown — manually recovered accounts become selectable immediately on every instance. - Codex credential batch import (OAuth/PAT); PAT revocation detection unified inside codex-sdk (AT-401 classifier owns the death codeset, wired to the fatal-disable callback), and WS death-frame classification delegates to the same source.
- Per-account usage snapshots surfaced to admins (TTL-cached, bounded-concurrency fetch) with a batch aggregation endpoint;
raw_costflows end-to-end (usage logs → offline stats aggregation → API → UI). - Template model hard whitelist: non-matching models return 404; tier-2 selection narrows to full-model accounts.
- Admin console: glass design-system refresh with Apple palette, codex multi-format import dialog (CPA/folder), account usage detail dialog, cooldown badge with remaining time, client IP columns on all four log tables, composite-IN condition editors, custom 404 page.
- Contributor knowledge base: hierarchical
AGENTS.md(root / internal / web).
Changed
- codex-sdk updated to
aef6a68: exported auth-frame classifier and SDK-owned PAT death judgment (the gateway-side 401 heuristic was removed). - Snapshot rebuilds reuse previous instances so concurrency/reuse counters stay continuous; static views swap atomically; the scheduler's in-memory status/cooldown is the authoritative source shown in the account list.
- OpenAPI: the rules
when/thenschema reference is fully documented (kind values, model semantics, empty-array rejection, pure-passthrough meaning). - README gained a performance section (pprof CPU profile findings and measured GC-tuning numbers).
Fixed
- Freshly created users could hit a 401 on their first immediate request (the debounced JWT-snapshot reload lagged the creation) — user create/register/update now update the local snapshot instantly.
- Rule cooldown defects: cooldown-only punishments were silently dropped, ok-recovery could fire while a cooldown was still unexpired, and rebuilds lost in-memory cooldowns when the DB column was NULL; the
disabledaction now persists to the database. - Codex dial 4xx responses bypassed rule-engine punishment — they are now classified and punished like every other failure surface.
- Protocol conversion fallback missed
ErrNoAvailable, and group-level protocol-convert edits did not propagate to key metadata (now registered incrementally with the Keys NOTIFY bit). - Web: a dead import broke the production build (
tsc), the user-profile page lacked its breadcrumb, the bucket time column truncated by granularity, the mobile menu lost its dropdown context, and several table-styling regressions.
v0.0.1-beta.3
[v0.0.1-beta.3] - 2026-08-17
Added
- Client IP capture in usage and error logs (
client_ipcolumn on both tables, exposed via the admin/user log APIs):proxy.behind_cdn(default false) enables vendor header detection —CF-Connecting-IP→True-Client-IP→X-Real-IPin order,RemoteAddrfallback; off =RemoteAddronly. Zero-allocation extraction on the request path. - Pagination (
limit/offset) for the redemption-code usage audit endpoint — previously silently truncated at 20 rows. - Masked admin key listing (
GET /admin/keys) with name/user/group filters and pagination. - WS upgrade handshake timeout (15 s) — a black-hole upstream can no longer pin concurrency slots forever.
- Service-side search for the logs filters and a unified ScrollArea scrollbar across the admin UI.
Changed
- Forwarding pipeline unified into a single shared skeleton:
handleFormat/HandleSearch/HandleResponsesWSnow share one guard stage (auth → quota → balance → concurrency gate → rate limit) and one failover loop, with per-format differences confined to two narrow interfaces (attempt + sink). Warn wording, per-format prechecks and the WS exhaustion frame stay format-specific. - WS relay (responses-ws and codex variants) unified onto a 5-method transport interface — the two 200-line concurrent state machines are now one skeleton plus thin adapters.
- Key/group lifecycle semantics hardened: soft-deleted keys can no longer be resurrected through update/rotate (404), soft-deleted groups reject key creation and assignment (404), group deletion validates account membership (409, including the batch path), key updates are patch-based (only provided fields are written), and assignment replacement is transactional.
- Admin role revocation takes effect immediately: the admin auth path now trusts the snapshot role (fail-closed when the snapshot is missing) instead of the 24 h JWT claim.
- WS relay goroutines are panic-contained per connection (log + orderly teardown) instead of crashing the whole process; the panic log now includes a stack trace and no longer writes a 500 body into an already-started SSE stream.
- Admin/user list endpoints clamp
limitto 200; strict JSON decoding rejects unknown fields and trailing garbage with a 400. - Config validation is fail-fast for
proxy.max_body_size(≥ 1) andupstream.idle_conn_timeout/dial_timeout(≥ 1 ms). - User-visible error frames never carry SDK/connection internals: codex fatal/4xx paths use fixed gateway messages, the aiclient 4xx fallback keeps internal text in logs only, and images SSE error frames use a fixed message.
- JSON processing family converged: single-pass top-level extraction (4 full-document scans → 2), byte-level sjson rewrites (preserving >2^53 integer precision), byte-anchored event-type detection, and a usage pre-filter on the chat path — all pinned by zero-allocation assertions.
- Web frontend: poll failures keep stale data with a warning bar instead of replacing the whole page; log filter inputs are debounced (300 ms); settings render unknown keys in a fallback card.
Fixed
- SSE long-line truncation zeroing billing/quota on long responses (line-continuation state machine).
- WS gateway-credential passthrough:
X-Api-Keyis now stripped from the upstream handshake likeAuthorization. - WS path ignoring
service_tier— tier extraction, strip/reject policy and billing tier are now applied on the WS first frame. - Converted-path
tt = 0never deducting quota — all three exit points mirror the nativett = it + ot. failover_attempts = 0leaking concurrency slots permanently (validated ≥ 1, plus a defensive release on the exhaust path).- Scheduler resurrection race:
apply/FailAccountare now copy-on-write CAS with a disabled absorbing state. - Anthropic SDK validation errors classified as 4xx instead of network errors.
- Test flakes: stats-agg first-round assertion timing, and the user-key lifecycle list assertion depending on fake-store map iteration order.
v0.0.1-beta.2
[v0.0.1-beta.2] - 2026-08-15
Added
- HTTP
/responsesmetadata alignment with the real codex client: per-requestturn_id(UUIDv7) plus the static identity key set injected on the HTTP path, with passthrough short-circuit when the request already carriesclient_metadata. x-codex-turn-stateresponse-header capture and same-turn replay on the HTTP path (turn-state double-face alignment with real codex behavior).- First-user admin bootstrap: the first account to register on a fresh database automatically becomes
platform_admin;ADMIN_TOKENis now optional. - GC optimization batch: zero-allocation single-pass usage extraction and per-stream relay channel merging.
- GC tuning hooks (
GOGC/GOMEMLIMIT) wired through compose and.env.example, with load-test numbers documented (off by default).
Changed
- codex-sdk updated to latest master (HTTP metadata injection, WS turn-state verification, pre-filter short-circuit).
- Deployment layout:
compose.ymlmoved to the repository root — plaindocker compose upnow works with root.envauto-load;deploy/holds the production config template and the data directory.
Fixed
- Deterministic CI: PostgreSQL partition fixtures now explicitly pre-create the fixed-date partitions they write into; the transport pool-reuse test proves 16 in-use connections via a barrier-controlled upstream instead of inferring pool capacity from dial counts.
- Dependency security: nanoid (CVE) and js-yaml (CVE-2026-59870
!!omapReDoS) upgraded via pnpm overrides. - Load-test tooling: NOTIFY snapshot-window 401s on fresh user creation and USD pricing units for key fill.
v0.0.1-beta.1
[v0.0.1-beta.1] - 2026-08-15
First public beta release.
Breaking
- No migration path. Databases and configurations are not backward-compatible across versions. Upgrading requires a brand-new database and a re-checked configuration (fresh setup) — no upgrade or migration tooling is provided.
Added
- AI gateway core: OpenAI Responses API (REST + WebSocket), Anthropic Messages API, OpenAI Chat Completions API, OpenAI Images API, Codex web search, and the OpenAI-compatible model list behind a single entry point, with model routing, quotas, usage accounting, and a rules engine (routing, rate limiting, 429/error backoff).
- Embedded admin console (React,
/admin) with a full OpenAPI-defined admin API: template/account management, per-user balance and FEFO temp quotas, usage statistics and billing, pricing sync from litellm. - SDK integration: auth lifecycle (credential rotation with expiry preservation, failure recovery), WebSocket business-frame handling, and SDK-grade HTTP client connection pooling.
- Real-PostgreSQL test base for repository/integration tests (dedicated test database; tests skip when
TEST_DATABASE_URLis unset).
Changed
- Ops convergence: config keys renamed for semantics (
stats_flush_interval→quota_flush_interval), fail-fast startup validation (unknown keys and placeholder secrets rejected), billing enabled by default. - Usage statistics moved to an offline aggregation worker; database tables are created fresh — legacy "align-patch" compatibility was removed (all tables are new).
Fixed
- Load-test storm fixes: partition drift (fresh-database policy), oversized batched flushes, missing event-name frames in streaming conversion, rejection storms, and shutdown truncation of in-flight batches.
- resp-ws 501 (HTTP Hijacker forwarding), SDK HTTP-client connection storms, and the
clientForrace. - Stats/overview display-caliber fixes (USD unit, TTFT metrics with histogram interpolation, compact number formatting).