Docs

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-auth now verifies the token's exp (expiration) and nbf (not-before) claims by default. Previously, a consumer that did not set claims_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 with HTTP 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_verify explicitly in the consumer configuration.

  • Batch Requests

    Upgrade note

    The batch-requests plugin now bounds the size of a batch. The number of pipelined sub-requests is limited by a new max_pipeline_items plugin-metadata option (default 1000); a batch that exceeds the limit is rejected with HTTP 400. Pipeline entries that contain fields other than the documented ones are now rejected, and the per-batch timeout must be at least 1 millisecond.

    If you send batches larger than 1000 sub-requests, raise max_pipeline_items in 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-metadata master_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_body is enabled, the body is limited by a new max_req_body_size option (default 524288 bytes, i.e. 512 KiB).
  • forward-auth, ai-proxy, ai-proxy-multi: the body is limited by a new max_req_body_size option (default 67108864 bytes, i.e. 64 MiB); requests over the limit are rejected with HTTP 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-advanced are now available directly in limit-count: the redis-sentinel policy, sliding-window counting (window_type set to sliding), multiple independent limits in a single configuration (rules), count and time_window driven by NGINX variables (for example, per-consumer dynamic quotas), and delayed Redis synchronization (sync_interval).
  • AI content security plugins (ai-aliyun-content-moderation, ai-aws-content-moderation, ai-prompt-guard)
    • Added a fail_mode option (skip, warn, or error; default skip) that controls how the plugin handles non-AI or non-JSON requests when it is bound at the Consumer level. With skip, such requests pass through unchecked; warn additionally logs a warning; error rejects them. This avoids errors when a Consumer-bound moderation plugin receives ordinary, non-AI traffic.
  • AI Proxy (opens in Plugin Hub docs)
    • Added built-in NGINX variables that describe each LLM request, for use in access_log formats 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.
  • 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 as apisix_llm_latency{type="ttft"} for streaming requests. Histogram buckets are configurable via plugin_attr.prometheus.
    • Added mcp_request_type and mcp_tool_name labels to the http_status, http_latency, and bandwidth metrics, so MCP (Model Context Protocol) traffic can be broken down by request type (tools/list or tools/call) and by tool name. Both labels can be turned off via disabled_labels.
    • Reduced per-request overhead in the metrics logging phase by caching the disabled-labels map instead of rebuilding it on every request.
  • AI Proxy Multi (opens in Plugin Hub docs)
    • Added max_retries and retry_on_failure_within_ms to the fallback mechanism. max_retries caps how many additional instances a request retries after a failure; retry_on_failure_within_ms only 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).
  • OpenID Connect (opens in Plugin Hub docs)
    • Exposed the lua-resty-session options under session (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 legacy session.cookie.lifetime is deprecated but still honored (mapped to absolute_timeout).
    • client_secret is now optional for flows that only verify tokens locally and never call the identity provider (bearer_only with public_key or use_jwks, private_key_jwt, and public-client PKCE). It remains required for session/callback and introspection flows.
  • Kafka Logger (opens in Plugin Hub docs)
    • Added an api_version option (0, 1, or 2; default 1) for the Kafka produce protocol. Set api_version to 2 so that brokers record the real message timestamp (Kafka 0.10 and later); the default 1 preserves wire compatibility.

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 500 instead of rejecting the request. Malformed signatures are now rejected with HTTP 401.
  • AI Proxy (opens in Plugin Hub docs)
    • Fixed issue: In passthrough mode, the upstream request was always sent as POST and 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 500 instead of HTTP 504. Upstream LLM timeouts now correctly return HTTP 504.
  • 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.query or auth.header. Both issues are fixed, so failover and health checking work as configured.
  • Graphql Proxy Cache (opens in Plugin Hub docs)
    • Fixed issue: After Vary support was added to the in-memory cache, a PURGE request cleared only the legacy cache slot and left the per-Vary variants cached, so stale responses were still served until their TTL expired. PURGE now clears all variants.
  • 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 returns HTTP 400 instead of HTTP 500.
  • 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_version to 2 makes brokers record the real timestamp.
  • 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.
  • 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.
  • 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.
  • Request ID (opens in Plugin Hub docs)
    • Fixed issue: With algorithm set to nanoid, 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.
  • OPA (opens in Plugin Hub docs)
    • Fixed issue: With send_headers_upstream configured, 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.
  • 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 Origin header returned HTTP 500 when allow_origins_by_regex was configured. Such requests are now handled without error.
  • 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 500 instead of HTTP 401. It now returns HTTP 401.
  • DingTalk Auth
    • Fixed issue: A client could supply a forged X-Userinfo header that was forwarded upstream. The plugin now clears any client-supplied X-Userinfo header before authentication, so upstream services only receive plugin-verified identity information.
  • 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 POST with an empty body returned HTTP 500 instead of HTTP 400. Empty-body logout requests are now rejected with HTTP 400.
  • Limit Conn (opens in Plugin Hub docs)
    • Fixed issue: A dynamic burst value (from an NGINX variable) that resolved to 0 was rejected with HTTP 500, even though a static burst of 0 is valid. A variable burst of 0 is now accepted.
  • 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, or limit-conn counters could be written to the wrong Redis database. Connections are now isolated by address, database, credentials, and TLS settings, including the redis-sentinel policy.
  • Fixed issue: After set_header was called with a different letter case than an existing header, the cached header table kept both entries, so plugins that iterate cached headers (such as ext-plugin-post-resp) could forward the stale value upstream. Cached header keys are now normalized.
  • Fixed issue: When the workflow plugin 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 PATCH re-encrypted already-encrypted plugin fields, so a PATCH on any field could corrupt stored secrets (such as key-auth keys). 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.