API7 Gateway 3.10.1
JWT Auth — jwt-auth now verifies the token's exp (expiration) and nbf (not-before) claims by default.
Release Date: 2026-06-15
Breaking Changes
Plugins
-
JWT Auth (opens in Plugin Hub docs)
Upgrade note
jwt-authnow verifies the token'sexp(expiration) andnbf(not-before) claims by default. Previously, a consumer that did not setclaims_to_verify(or set it to an empty list) accepted any correctly signed token, including expired ones. After the Data Plane is upgraded, such tokens are rejected withHTTP 401.If you relied on expired tokens being accepted, account for this behavior change before upgrading. To keep verifying only specific claims, set
claims_to_verifyexplicitly in the consumer configuration. -
Batch Requests
Upgrade note
The
batch-requestsplugin now bounds the size of a batch. The number of pipelined sub-requests is limited by a newmax_pipeline_itemsplugin-metadata option (default1000); a batch that exceeds the limit is rejected withHTTP 400. Pipeline entries that contain fields other than the documented ones are now rejected, and the per-batchtimeoutmust be at least1millisecond.If you send batches larger than 1000 sub-requests, raise
max_pipeline_itemsin the plugin metadata. If your clients send entries with undocumented fields, remove them before upgrading.
Upgrade Notes
Upgrade note — additional plugin secret fields are encrypted at rest
The Control Plane now encrypts additional credential-bearing plugin fields at rest. Because API7 EE upgrades the Control Plane before the Data Plane, during the upgrade window a 3.10.1 Control Plane encrypts these fields while an older 3.10.0 Data Plane cannot decrypt them, which can cause the affected plugins to fail until the Data Plane is also upgraded.
The newly encrypted fields, by plugin, are:
- http-logger:
auth_header - kafka-logger:
brokers.sasl_config.password - splunk-hec-logging:
endpoint.token - loggly:
customer_token - openfunction:
authorization.service_token - azure-functions:
authorization.apikey, and the plugin-metadatamaster_apikey - ai-aws-content-moderation:
comprehend.secret_access_key - openid-connect:
session.secret - error-log-logger (plugin metadata):
kafka.brokers.sasl_config.password
If you use any of these plugins with the listed fields, upgrade the Data Plane to 3.10.1 promptly after the Control Plane, and avoid editing those plugins until both sides are on 3.10.1.
Upgrade note — limit-count now includes the advanced rate-limiting features
All features that were previously exclusive to limit-count-advanced — the redis-sentinel policy, sliding-window counting, multiple independent limits in one configuration (rules), dynamic count and time_window from NGINX variables, and delayed Redis synchronization (sync_interval) — are now built into the limit-count plugin. Existing limit-count-advanced configurations continue to work unchanged (the plugin is retained as a thin wrapper), so no configuration migration is required.
One upgrade-time effect: for Redis-backed policies, the counter storage format changed and counter keys are now versioned. Existing counters are not migrated — they expire on their own TTL — so rate-limit counters reset once at the moment of upgrade. (The local policy already resets on restart.) Expect a brief counter-reset window; no action is required.
Upgrade note — request body size limits added to several plugins
To bound memory usage, several plugins now reject over-large request bodies before buffering them:
- hmac-auth: when
validate_request_bodyis enabled, the body is limited by a newmax_req_body_sizeoption (default524288bytes, i.e. 512 KiB). - forward-auth, ai-proxy, ai-proxy-multi: the body is limited by a new
max_req_body_sizeoption (default67108864bytes, i.e. 64 MiB); requests over the limit are rejected withHTTP 413.
These defaults are above the NGINX default client_max_body_size (1 MiB), so most deployments are unaffected. If you legitimately handle larger bodies on routes that use these plugins (and have raised client_max_body_size accordingly), raise max_req_body_size to match.
Upgrade note — Developer Portal credential secrets are shown only once
In the Developer Portal, the key-auth key and basic-auth password of a credential are now returned only once, when the credential is created or regenerated. They are no longer included in credential read or list responses, matching the existing behavior of the OAuth client_secret. Basic-auth usernames remain visible.
Copy and store the secret when it is first shown. If a secret is lost, regenerate the credential to obtain a new value. Any integration that read these secrets back from the credential read or list API must be updated to capture them at creation time.
Upgrade note — Prometheus TTFT metric replaced by a label
The standalone apisix_llm_ttft metric has been replaced by apisix_llm_latency{type="ttft"}, mirroring the structure of apisix_http_latency. If you have Prometheus queries or Grafana dashboards that reference apisix_llm_ttft, update them to select the ttft value of the type label on apisix_llm_latency.
Features
Plugins
- Limit Count (opens in Plugin Hub docs)
- The advanced rate-limiting capabilities formerly provided only by
limit-count-advancedare now available directly inlimit-count: theredis-sentinelpolicy, sliding-window counting (window_typeset tosliding), multiple independent limits in a single configuration (rules),countandtime_windowdriven by NGINX variables (for example, per-consumer dynamic quotas), and delayed Redis synchronization (sync_interval).
- The advanced rate-limiting capabilities formerly provided only by
- AI content security plugins (
ai-aliyun-content-moderation,ai-aws-content-moderation,ai-prompt-guard)- Added a
fail_modeoption (skip,warn, orerror; defaultskip) that controls how the plugin handles non-AI or non-JSON requests when it is bound at the Consumer level. Withskip, such requests pass through unchecked;warnadditionally logs a warning;errorrejects them. This avoids errors when a Consumer-bound moderation plugin receives ordinary, non-AI traffic.
- Added a
- AI Proxy (opens in Plugin Hub docs)
- Added built-in NGINX variables that describe each LLM request, for use in
access_logformats and logger plugins:$llm_total_tokens,$llm_stream,$llm_has_tool_calls,$llm_tool_count,$llm_end_user_id,$llm_cache_read_input_tokens,$llm_cache_creation_input_tokens, and$llm_reasoning_tokens. Values are mapped for OpenAI (Chat and Responses), Anthropic, and DeepSeek.
- Added built-in NGINX variables that describe each LLM request, for use in
- Prometheus (opens in Plugin Hub docs)
- Added LLM observability metrics: per-request prompt-token and completion-token distribution histograms (
apisix_llm_prompt_tokens_dist,apisix_llm_completion_tokens_dist), a total-latency histogram, and time-to-first-token exposed asapisix_llm_latency{type="ttft"}for streaming requests. Histogram buckets are configurable viaplugin_attr.prometheus. - Added
mcp_request_typeandmcp_tool_namelabels to thehttp_status,http_latency, andbandwidthmetrics, so MCP (Model Context Protocol) traffic can be broken down by request type (tools/listortools/call) and by tool name. Both labels can be turned off viadisabled_labels. - Reduced per-request overhead in the metrics logging phase by caching the disabled-labels map instead of rebuilding it on every request.
- Added LLM observability metrics: per-request prompt-token and completion-token distribution histograms (
- AI Proxy Multi (opens in Plugin Hub docs)
- Added
max_retriesandretry_on_failure_within_msto the fallback mechanism.max_retriescaps how many additional instances a request retries after a failure;retry_on_failure_within_msonly falls back when the upstream failed within the given time, so slow failures are returned to the client instead of being retried (avoiding doubled latency on long-running LLM requests).
- Added
- OpenID Connect (opens in Plugin Hub docs)
- Exposed the
lua-resty-sessionoptions undersession(cookie name, path, domain,secure,http_only,same_site, and the idling, rolling, and absolute timeouts), so you can customize the session cookie name and set session lifetimes that take effect. The legacysession.cookie.lifetimeis deprecated but still honored (mapped toabsolute_timeout). client_secretis now optional for flows that only verify tokens locally and never call the identity provider (bearer_onlywithpublic_keyoruse_jwks,private_key_jwt, and public-client PKCE). It remains required for session/callback and introspection flows.
- Exposed the
- Kafka Logger (opens in Plugin Hub docs)
- Added an
api_versionoption (0,1, or2; default1) for the Kafka produce protocol. Setapi_versionto2so that brokers record the real message timestamp (Kafka 0.10 and later); the default1preserves wire compatibility.
- Added an
Developer Portal
- Two-factor authentication: developers can secure their account with a TOTP authenticator app. 2FA is enabled from the account security settings (confirm password, scan the QR code) and can be disabled there; sign-in then prompts for a six-digit code.
- Dark mode: the portal supports light, dark, and system themes with a toggle in the header, applied across the UI, the API-usage chart, and the docs site, and persisted across visits.
- Platform-admin console: an admin area (restricted to the configured admin users) adds a Users page to list, search, and paginate portal users, change a user's role, ban or unban, and delete users; the Organizations page can be filtered by user membership.
- Approvals in the Developer Portal: platform admins can review and act on developer-registration and API-product subscription requests from a new Approvals page in the portal. Approvals share a single source of truth with the Dashboard, and the acting admin's identity is recorded with each decision (the Dashboard's Approvals list now shows the real admin name for decisions made in the portal).
- Policy-based SSO sign-in: sign-in routes each user to the correct method (credentials, magic link, or SSO) based on the email entered. Administrators can map email domains to SSO providers, including anchored, case-insensitive regular-expression patterns, so one rule can cover many domains.
- In-portal documentation site: the portal now hosts a Markdown documentation site at
/docs, themed to match the portal, with a collapsible sidebar, table of contents, code-copy buttons, a "Copy page" menu, and built-in full-text search with highlighted result snippets. - Signup Terms of Service: new users must accept a Terms of Service agreement to complete signup; the ToS URL is configurable.
- Simplified organization creation: the "Create organization" dialog no longer asks for a slug — the identifier is assigned automatically from the name.
Fixes
Plugins
- JWT Auth (opens in Plugin Hub docs)
- Fixed issue: A token whose signature was malformed (wrong length or not valid base64url) caused the verifier to raise an error and return
HTTP 500instead of rejecting the request. Malformed signatures are now rejected withHTTP 401.
- Fixed issue: A token whose signature was malformed (wrong length or not valid base64url) caused the verifier to raise an error and return
- AI Proxy (opens in Plugin Hub docs)
- Fixed issue: In passthrough mode, the upstream request was always sent as
POSTand the client's query string was dropped, breaking providers that require query parameters on other methods (for example, Azure OpenAI's?api-version=). The client's method and query string are now forwarded. - Fixed issue: A timeout while reaching an upstream LLM (such as a DNS-resolution timeout) returned
HTTP 500instead ofHTTP 504. Upstream LLM timeouts now correctly returnHTTP 504.
- Fixed issue: In passthrough mode, the upstream request was always sent as
- AI Proxy Multi (opens in Plugin Hub docs)
- Fixed issue: After per-instance health checkers were created, the cached server picker was not rebuilt, so a worker could keep routing part of its traffic to instances already marked unhealthy; active health-check probes could also corrupt the probe path for instances configured with
auth.queryorauth.header. Both issues are fixed, so failover and health checking work as configured.
- Fixed issue: After per-instance health checkers were created, the cached server picker was not rebuilt, so a worker could keep routing part of its traffic to instances already marked unhealthy; active health-check probes could also corrupt the probe path for instances configured with
- Graphql Proxy Cache (opens in Plugin Hub docs)
- Fixed issue: After
Varysupport was added to the in-memory cache, aPURGErequest cleared only the legacy cache slot and left the per-Varyvariants cached, so stale responses were still served until their TTL expired.PURGEnow clears all variants.
- Fixed issue: After
- Body Transformer (opens in Plugin Hub docs)
- Fixed issue: The XML-to-JSON transform intermittently dropped namespaced keys on some worker processes, causing SOAP transforms to fail with
attempt to index field 'Body' (a nil value). Namespaced keys are now always preserved. Malformed multipart input now returnsHTTP 400instead ofHTTP 500.
- Fixed issue: The XML-to-JSON transform intermittently dropped namespaced keys on some worker processes, causing SOAP transforms to fail with
- Kafka Logger (opens in Plugin Hub docs)
- Fixed issue: Messages were stored by brokers with no usable timestamp (rendered as 1970-01-01) because the Kafka produce API version could not be configured. Setting the new
api_versionto2makes brokers record the real timestamp.
- Fixed issue: Messages were stored by brokers with no usable timestamp (rendered as 1970-01-01) because the Kafka produce API version could not be configured. Setting the new
- Elasticsearch Logger (opens in Plugin Hub docs)
- Fixed issue: A dynamic index name using certain
{time_format}placeholders could produce a corrupted index name. Invalid time formats now fall back to an empty value (with a log message) instead of corrupting the index name.
- Fixed issue: A dynamic index name using certain
- AWS Lambda (opens in Plugin Hub docs)
- Fixed issue: With IAM (SigV4) authentication, requests whose query string contained characters that need escaping, multiple values, or valueless keys produced a signature mismatch (
InvalidSignatureException). The canonical query string is now built per the SigV4 specification.
- Fixed issue: With IAM (SigV4) authentication, requests whose query string contained characters that need escaping, multiple values, or valueless keys produced a signature mismatch (
- Proxy Mirror (opens in Plugin Hub docs)
- Fixed issue: Mirroring a gRPC request sent the internal location name as the request path, so the mirror backend rejected every call with
UNIMPLEMENTED. The original gRPC method path is now used for the mirrored request.
- Fixed issue: Mirroring a gRPC request sent the internal location name as the request path, so the mirror backend rejected every call with
- Request ID (opens in Plugin Hub docs)
- Fixed issue: With
algorithmset tonanoid, the generator produced a high rate of duplicate and malformed IDs and leaked a file descriptor per ID. It is replaced with a CSPRNG-based generator; the ID format is unchanged and IDs are now unique and well-formed.
- Fixed issue: With
- OPA (opens in Plugin Hub docs)
- Fixed issue: With
send_headers_upstreamconfigured, a header that the OPA server did not return was left with the client-supplied value on the upstream request instead of being cleared. Such headers are now cleared.
- Fixed issue: With
- SAML Auth (opens in Plugin Hub docs)
- Fixed issue: Debug logging was unintentionally left enabled, and an authentication failure could pass the request through instead of returning an error. Debug mode is now off, and authentication failures return an explicit error.
- CORS (opens in Plugin Hub docs)
- Fixed issue: A request with no
Originheader returnedHTTP 500whenallow_origins_by_regexwas configured. Such requests are now handled without error.
- Fixed issue: A request with no
- Multi Auth (opens in Plugin Hub docs)
- Fixed issue: When an underlying auth plugin returned a status with no error message, the plugin returned
HTTP 500instead ofHTTP 401. It now returnsHTTP 401.
- Fixed issue: When an underlying auth plugin returned a status with no error message, the plugin returned
- DingTalk Auth
- Fixed issue: A client could supply a forged
X-Userinfoheader that was forwarded upstream. The plugin now clears any client-suppliedX-Userinfoheader before authentication, so upstream services only receive plugin-verified identity information.
- Fixed issue: A client could supply a forged
- Authz Casdoor
- Fixed issue: The login session was not bound to the Casdoor token's lifetime and fell back to the session library default, so sessions could outlive their tokens. Sessions now expire when the Casdoor token expires.
- CAS Auth
- Fixed issue: A single-logout
POSTwith an empty body returnedHTTP 500instead ofHTTP 400. Empty-body logout requests are now rejected withHTTP 400.
- Fixed issue: A single-logout
- Limit Conn (opens in Plugin Hub docs)
- Fixed issue: A dynamic
burstvalue (from an NGINX variable) that resolved to0was rejected withHTTP 500, even though a staticburstof0is valid. A variableburstof0is now accepted.
- Fixed issue: A dynamic
- Limit Req (opens in Plugin Hub docs)
- Fixed issue: Under concurrent load, the Redis-backed rate limit could be exceeded because the read and write were not atomic. Enforcement now uses a single atomic operation.
Data Plane
- Fixed issue: Redis connections were pooled by address only, so two plugin configurations pointing at the same Redis server with different databases, credentials, or TLS settings could reuse each other's connections — for example,
limit-count,limit-req, orlimit-conncounters could be written to the wrong Redis database. Connections are now isolated by address, database, credentials, and TLS settings, including theredis-sentinelpolicy. - Fixed issue: After
set_headerwas called with a different letter case than an existing header, the cached header table kept both entries, so plugins that iterate cached headers (such asext-plugin-post-resp) could forward the stale value upstream. Cached header keys are now normalized. - Fixed issue: When the
workflowplugin ran in a global rule and a route plugin completed the request during the rewrite phase (for example, a CORS preflight), the log phase logged an error on every such request. The log phase now exits cleanly when its context is absent. - Fixed issue: Enabling Nacos service discovery with the stream subsystem aborted the stream worker at startup because a required shared dictionary was not declared in the stream subsystem; it is now declared. A separate error-handling bug that turned a Nacos registry-creation failure into a worker crash is also fixed.
- Fixed issue: With Consul service discovery, a single malformed node entry caused the remaining nodes of the service to be discarded (and could drop the entire service, producing "no valid upstream node"). Only the invalid node is now skipped.
- Fixed issue: Resolving an AWS Secrets Manager reference failed when the secret name contained a slash, because the lookup split the path at the first slash. Secret names containing slashes now resolve correctly.
- Fixed issue: An Admin API
PATCHre-encrypted already-encrypted plugin fields, so aPATCHon any field could corrupt stored secrets (such askey-authkeys). Encrypted fields are now decrypted before the merge and re-encrypted exactly once.
Developer Portal
- Fixed issue: Organization invitations did not honor the email-verification setting. Invitations now follow the configured email-verification behavior.
- Fixed issue: For a developer who belongs to multiple organizations, the portal could use the wrong organization context. The developer identity is now resolved from the active organization.
- Fixed issue: Several dialog widths were inconsistent across the portal; they are now unified.