diff --git a/docs/content/docs/en/reference/api/clients.mdx b/docs/content/docs/en/reference/api/clients.mdx index ac25bf3a7..d4e9f8269 100644 --- a/docs/content/docs/en/reference/api/clients.mdx +++ b/docs/content/docs/en/reference/api/clients.mdx @@ -28,10 +28,9 @@ _openapi: url: '#fetch-one-client-by-email-including-the-inbound-ids-and-external-config-ids-it-is-attached-to' - depth: 2 title: Create a new client and attach it to one or more inbounds in a single - call. Body is JSON. Per-protocol secrets (UUID for VLESS/VMess, password - for Trojan/Shadowsocks, auth for Hysteria) are generated server-side - when omitted, so callers can send only the universal fields. - url: '#create-a-new-client-and-attach-it-to-one-or-more-inbounds-in-a-single-call-body-is-json-per-protocol-secrets-uuid-for-vlessvmess-password-for-trojanshadowsocks-auth-for-hysteria-are-generated-server-side-when-omitted-so-callers-can-send-only-the-universal-fields' + call. Body is JSON. Per-protocol secrets are generated server-side when + omitted, so callers can send only the universal fields. + url: '#create-a-new-client-and-attach-it-to-one-or-more-inbounds-in-a-single-call-body-is-json-per-protocol-secrets-are-generated-server-side-when-omitted-so-callers-can-send-only-the-universal-fields' - depth: 2 title: Update an existing client by email. Changes propagate to every attached inbound. Body is the JSON client payload — supply the full set of fields @@ -278,11 +277,9 @@ _openapi: config IDs it is attached to. id: fetch-one-client-by-email-including-the-inbound-ids-and-external-config-ids-it-is-attached-to - content: Create a new client and attach it to one or more inbounds in a single - call. Body is JSON. Per-protocol secrets (UUID for VLESS/VMess, - password for Trojan/Shadowsocks, auth for Hysteria) are generated - server-side when omitted, so callers can send only the universal - fields. - id: create-a-new-client-and-attach-it-to-one-or-more-inbounds-in-a-single-call-body-is-json-per-protocol-secrets-uuid-for-vlessvmess-password-for-trojanshadowsocks-auth-for-hysteria-are-generated-server-side-when-omitted-so-callers-can-send-only-the-universal-fields + call. Body is JSON. Per-protocol secrets are generated server-side + when omitted, so callers can send only the universal fields. + id: create-a-new-client-and-attach-it-to-one-or-more-inbounds-in-a-single-call-body-is-json-per-protocol-secrets-are-generated-server-side-when-omitted-so-callers-can-send-only-the-universal-fields - content: Update an existing client by email. Changes propagate to every attached inbound. Body is the JSON client payload — supply the full set of fields you want to keep (the server replaces the row, it does not @@ -477,7 +474,57 @@ _openapi: - content: Remove a single registered HWID device by its id, freeing one slot under the HWID limit. id: remove-a-single-registered-hwid-device-by-its-id-freeing-one-slot-under-the-hwid-limit - contents: [] + contents: + - content: >- + Fields the server fills in when they are omitted — a valid value sent + by the caller is never overwritten. Re-adding an email that already + exists, with its stored `subId`, reuses the stored `id`, `password`, + `auth` and `secret` instead of minting new ones, so the identity stays + in sync across its inbounds. + + + - **VLESS / VMess** — `id`, a fresh UUID + + - **Trojan** — `password` + + - **Shadowsocks** — `password`. On a `2022-blake3-*` inbound a + supplied password that does not base64-decode to the key length of the + cipher (16 or 32 bytes) is replaced by a generated key and the call + still succeeds, so read the client back if you did not let the server + pick. Legacy ciphers keep any non-empty password + + - **Hysteria** — `auth` + + - **mtproto** — `secret`, a FakeTLS secret derived from the fronting + domain of the inbound, or from `www.cloudflare.com` when it has none + + - **WireGuard** — `privateKey` and `publicKey` when both are blank, or + `publicKey` alone when only a `privateKey` was sent, plus + `allowedIPs`: one free `/32` taken from the /24 the existing peers of + that inbound already sit in, or from `10.0.0.0/24` when it has none + + + Accepted on the same body but never generated: `preSharedKey` and + `keepAlive` (WireGuard), `adTag` (mtproto). + + + WireGuard is the only one of these that can fail. Allocation widens + the search to the containing /16 before giving up with `wireguard: no + free address available in `, and an `allowedIPs` supplied by + the caller is validated instead of allocated: `wireguard: allowedIPs + entry already used by another client:
` when a different + client of that same inbound already holds it. The check is per + inbound, so the same address on two different inbounds is accepted. + The same validation runs on POST /panel/api/clients/{email}/attach, + where a client that already carries an address brings it along. + heading: create-a-new-client-and-attach-it-to-one-or-more-inbounds-in-a-single-call-body-is-json-per-protocol-secrets-are-generated-server-side-when-omitted-so-callers-can-send-only-the-universal-fields + - content: 'A WireGuard client brings its stored `allowedIPs` into the new inbound + instead of being given a fresh address, so the call fails with + `wireguard: allowedIPs entry already used by another client: +
` when a different client of the target inbound already holds + it. Free the address on that inbound first — see POST + /panel/api/clients/add for the full rule.' + heading: attach-an-existing-client-to-one-or-more-additional-inbounds-body-is-json --- {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} diff --git a/docs/content/docs/fa/reference/api/clients.mdx b/docs/content/docs/fa/reference/api/clients.mdx index 607d0a259..d945e333d 100644 --- a/docs/content/docs/fa/reference/api/clients.mdx +++ b/docs/content/docs/fa/reference/api/clients.mdx @@ -37,11 +37,10 @@ _openapi: - depth: 2 title: >- Create a new client and attach it to one or more inbounds in a single - call. Body is JSON. Per-protocol secrets (UUID for VLESS/VMess, password - for Trojan/Shadowsocks, auth for Hysteria) are generated server-side - when omitted, so callers can send only the universal fields. + call. Body is JSON. Per-protocol secrets are generated server-side when + omitted, so callers can send only the universal fields. url: >- - #create-a-new-client-and-attach-it-to-one-or-more-inbounds-in-a-single-call-body-is-json-per-protocol-secrets-uuid-for-vlessvmess-password-for-trojanshadowsocks-auth-for-hysteria-are-generated-server-side-when-omitted-so-callers-can-send-only-the-universal-fields + #create-a-new-client-and-attach-it-to-one-or-more-inbounds-in-a-single-call-body-is-json-per-protocol-secrets-are-generated-server-side-when-omitted-so-callers-can-send-only-the-universal-fields - depth: 2 title: >- Update an existing client by email. Changes propagate to every attached @@ -352,12 +351,10 @@ _openapi: fetch-one-client-by-email-including-the-inbound-ids-and-external-config-ids-it-is-attached-to - content: >- Create a new client and attach it to one or more inbounds in a single - call. Body is JSON. Per-protocol secrets (UUID for VLESS/VMess, - password for Trojan/Shadowsocks, auth for Hysteria) are generated - server-side when omitted, so callers can send only the universal - fields. + call. Body is JSON. Per-protocol secrets are generated server-side + when omitted, so callers can send only the universal fields. id: >- - create-a-new-client-and-attach-it-to-one-or-more-inbounds-in-a-single-call-body-is-json-per-protocol-secrets-uuid-for-vlessvmess-password-for-trojanshadowsocks-auth-for-hysteria-are-generated-server-side-when-omitted-so-callers-can-send-only-the-universal-fields + create-a-new-client-and-attach-it-to-one-or-more-inbounds-in-a-single-call-body-is-json-per-protocol-secrets-are-generated-server-side-when-omitted-so-callers-can-send-only-the-universal-fields - content: >- Update an existing client by email. Changes propagate to every attached inbound. Body is the JSON client payload — supply the full @@ -610,7 +607,57 @@ _openapi: dokodemo, tunnel) contribute nothing. id: >- return-every-url-for-one-client-across-all-attached-inbounds--the-same-strings-the-copy-url-button-copies-in-the-panel-ui-supported-protocols-vmess-vless-trojan-shadowsocks-hysteria-if-streamsettingsexternalproxy-is-set-returns-one-url-per-external-proxy-protocols-without-a-url-form-socks-http-mixed-wireguard-dokodemo-tunnel-contribute-nothing - contents: [] + contents: + - content: >- + Fields the server fills in when they are omitted — a valid value sent + by the caller is never overwritten. Re-adding an email that already + exists, with its stored `subId`, reuses the stored `id`, `password`, + `auth` and `secret` instead of minting new ones, so the identity stays + in sync across its inbounds. + + + - **VLESS / VMess** — `id`, a fresh UUID + + - **Trojan** — `password` + + - **Shadowsocks** — `password`. On a `2022-blake3-*` inbound a + supplied password that does not base64-decode to the key length of the + cipher (16 or 32 bytes) is replaced by a generated key and the call + still succeeds, so read the client back if you did not let the server + pick. Legacy ciphers keep any non-empty password + + - **Hysteria** — `auth` + + - **mtproto** — `secret`, a FakeTLS secret derived from the fronting + domain of the inbound, or from `www.cloudflare.com` when it has none + + - **WireGuard** — `privateKey` and `publicKey` when both are blank, or + `publicKey` alone when only a `privateKey` was sent, plus + `allowedIPs`: one free `/32` taken from the /24 the existing peers of + that inbound already sit in, or from `10.0.0.0/24` when it has none + + + Accepted on the same body but never generated: `preSharedKey` and + `keepAlive` (WireGuard), `adTag` (mtproto). + + + WireGuard is the only one of these that can fail. Allocation widens + the search to the containing /16 before giving up with `wireguard: no + free address available in `, and an `allowedIPs` supplied by + the caller is validated instead of allocated: `wireguard: allowedIPs + entry already used by another client:
` when a different + client of that same inbound already holds it. The check is per + inbound, so the same address on two different inbounds is accepted. + The same validation runs on POST /panel/api/clients/{email}/attach, + where a client that already carries an address brings it along. + heading: create-a-new-client-and-attach-it-to-one-or-more-inbounds-in-a-single-call-body-is-json-per-protocol-secrets-are-generated-server-side-when-omitted-so-callers-can-send-only-the-universal-fields + - content: 'A WireGuard client brings its stored `allowedIPs` into the new inbound + instead of being given a fresh address, so the call fails with + `wireguard: allowedIPs entry already used by another client: +
` when a different client of the target inbound already holds + it. Free the address on that inbound first — see POST + /panel/api/clients/add for the full rule.' + heading: attach-an-existing-client-to-one-or-more-additional-inbounds-body-is-json --- {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} diff --git a/docs/content/docs/ru/reference/api/clients.mdx b/docs/content/docs/ru/reference/api/clients.mdx index 9ddf09902..6aeaa0444 100644 --- a/docs/content/docs/ru/reference/api/clients.mdx +++ b/docs/content/docs/ru/reference/api/clients.mdx @@ -37,11 +37,10 @@ _openapi: - depth: 2 title: >- Create a new client and attach it to one or more inbounds in a single - call. Body is JSON. Per-protocol secrets (UUID for VLESS/VMess, password - for Trojan/Shadowsocks, auth for Hysteria) are generated server-side - when omitted, so callers can send only the universal fields. + call. Body is JSON. Per-protocol secrets are generated server-side when + omitted, so callers can send only the universal fields. url: >- - #create-a-new-client-and-attach-it-to-one-or-more-inbounds-in-a-single-call-body-is-json-per-protocol-secrets-uuid-for-vlessvmess-password-for-trojanshadowsocks-auth-for-hysteria-are-generated-server-side-when-omitted-so-callers-can-send-only-the-universal-fields + #create-a-new-client-and-attach-it-to-one-or-more-inbounds-in-a-single-call-body-is-json-per-protocol-secrets-are-generated-server-side-when-omitted-so-callers-can-send-only-the-universal-fields - depth: 2 title: >- Update an existing client by email. Changes propagate to every attached @@ -352,12 +351,10 @@ _openapi: fetch-one-client-by-email-including-the-inbound-ids-and-external-config-ids-it-is-attached-to - content: >- Create a new client and attach it to one or more inbounds in a single - call. Body is JSON. Per-protocol secrets (UUID for VLESS/VMess, - password for Trojan/Shadowsocks, auth for Hysteria) are generated - server-side when omitted, so callers can send only the universal - fields. + call. Body is JSON. Per-protocol secrets are generated server-side + when omitted, so callers can send only the universal fields. id: >- - create-a-new-client-and-attach-it-to-one-or-more-inbounds-in-a-single-call-body-is-json-per-protocol-secrets-uuid-for-vlessvmess-password-for-trojanshadowsocks-auth-for-hysteria-are-generated-server-side-when-omitted-so-callers-can-send-only-the-universal-fields + create-a-new-client-and-attach-it-to-one-or-more-inbounds-in-a-single-call-body-is-json-per-protocol-secrets-are-generated-server-side-when-omitted-so-callers-can-send-only-the-universal-fields - content: >- Update an existing client by email. Changes propagate to every attached inbound. Body is the JSON client payload — supply the full @@ -610,7 +607,57 @@ _openapi: dokodemo, tunnel) contribute nothing. id: >- return-every-url-for-one-client-across-all-attached-inbounds--the-same-strings-the-copy-url-button-copies-in-the-panel-ui-supported-protocols-vmess-vless-trojan-shadowsocks-hysteria-if-streamsettingsexternalproxy-is-set-returns-one-url-per-external-proxy-protocols-without-a-url-form-socks-http-mixed-wireguard-dokodemo-tunnel-contribute-nothing - contents: [] + contents: + - content: >- + Fields the server fills in when they are omitted — a valid value sent + by the caller is never overwritten. Re-adding an email that already + exists, with its stored `subId`, reuses the stored `id`, `password`, + `auth` and `secret` instead of minting new ones, so the identity stays + in sync across its inbounds. + + + - **VLESS / VMess** — `id`, a fresh UUID + + - **Trojan** — `password` + + - **Shadowsocks** — `password`. On a `2022-blake3-*` inbound a + supplied password that does not base64-decode to the key length of the + cipher (16 or 32 bytes) is replaced by a generated key and the call + still succeeds, so read the client back if you did not let the server + pick. Legacy ciphers keep any non-empty password + + - **Hysteria** — `auth` + + - **mtproto** — `secret`, a FakeTLS secret derived from the fronting + domain of the inbound, or from `www.cloudflare.com` when it has none + + - **WireGuard** — `privateKey` and `publicKey` when both are blank, or + `publicKey` alone when only a `privateKey` was sent, plus + `allowedIPs`: one free `/32` taken from the /24 the existing peers of + that inbound already sit in, or from `10.0.0.0/24` when it has none + + + Accepted on the same body but never generated: `preSharedKey` and + `keepAlive` (WireGuard), `adTag` (mtproto). + + + WireGuard is the only one of these that can fail. Allocation widens + the search to the containing /16 before giving up with `wireguard: no + free address available in `, and an `allowedIPs` supplied by + the caller is validated instead of allocated: `wireguard: allowedIPs + entry already used by another client:
` when a different + client of that same inbound already holds it. The check is per + inbound, so the same address on two different inbounds is accepted. + The same validation runs on POST /panel/api/clients/{email}/attach, + where a client that already carries an address brings it along. + heading: create-a-new-client-and-attach-it-to-one-or-more-inbounds-in-a-single-call-body-is-json-per-protocol-secrets-are-generated-server-side-when-omitted-so-callers-can-send-only-the-universal-fields + - content: 'A WireGuard client brings its stored `allowedIPs` into the new inbound + instead of being given a fresh address, so the call fails with + `wireguard: allowedIPs entry already used by another client: +
` when a different client of the target inbound already holds + it. Free the address on that inbound first — see POST + /panel/api/clients/add for the full rule.' + heading: attach-an-existing-client-to-one-or-more-additional-inbounds-body-is-json --- {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} diff --git a/docs/content/docs/zh/reference/api/clients.mdx b/docs/content/docs/zh/reference/api/clients.mdx index f61311e70..830cdf605 100644 --- a/docs/content/docs/zh/reference/api/clients.mdx +++ b/docs/content/docs/zh/reference/api/clients.mdx @@ -30,11 +30,9 @@ _openapi: #fetch-one-client-by-email-including-the-inbound-ids-and-external-config-ids-it-is-attached-to - depth: 2 title: >- - 在一次调用中创建一个新客户端并将其挂载到一个或多个入站。请求体为 JSON。各协议的密钥 - (VLESS/VMess 的 UUID、Trojan/Shadowsocks 的 password、Hysteria 的 auth)在 - 省略时由服务端生成,因此调用方只需发送通用字段。 + 在一次调用中创建一个新客户端并将其挂载到一个或多个入站。请求体为 JSON。各协议的密钥在省略时由服务端生成,因此调用方只需发送通用字段。 url: >- - #create-a-new-client-and-attach-it-to-one-or-more-inbounds-in-a-single-call-body-is-json-per-protocol-secrets-uuid-for-vlessvmess-password-for-trojanshadowsocks-auth-for-hysteria-are-generated-server-side-when-omitted-so-callers-can-send-only-the-universal-fields + #create-a-new-client-and-attach-it-to-one-or-more-inbounds-in-a-single-call-body-is-json-per-protocol-secrets-are-generated-server-side-when-omitted-so-callers-can-send-only-the-universal-fields - depth: 2 title: >- 按 email 更新现有客户端。变更会传播到每个挂载的入站。请求体为 JSON 客户端载荷—— @@ -290,11 +288,9 @@ _openapi: id: >- fetch-one-client-by-email-including-the-inbound-ids-and-external-config-ids-it-is-attached-to - content: >- - 在一次调用中创建一个新客户端并将其挂载到一个或多个入站。请求体为 JSON。各协议的密钥 - (VLESS/VMess 的 UUID、Trojan/Shadowsocks 的 password、Hysteria 的 auth)在 - 省略时由服务端生成,因此调用方只需发送通用字段。 + 在一次调用中创建一个新客户端并将其挂载到一个或多个入站。请求体为 JSON。各协议的密钥在省略时由服务端生成,因此调用方只需发送通用字段。 id: >- - create-a-new-client-and-attach-it-to-one-or-more-inbounds-in-a-single-call-body-is-json-per-protocol-secrets-uuid-for-vlessvmess-password-for-trojanshadowsocks-auth-for-hysteria-are-generated-server-side-when-omitted-so-callers-can-send-only-the-universal-fields + create-a-new-client-and-attach-it-to-one-or-more-inbounds-in-a-single-call-body-is-json-per-protocol-secrets-are-generated-server-side-when-omitted-so-callers-can-send-only-the-universal-fields - content: >- 按 email 更新现有客户端。变更会传播到每个挂载的入站。请求体为 JSON 客户端载荷—— 请提供你希望保留的完整字段集(服务端会替换整条记录,而非局部更新)。 @@ -493,7 +489,32 @@ _openapi: (socks、http、mixed、wireguard、dokodemo、tunnel)不产生任何内容。 id: >- return-every-url-for-one-client-across-all-attached-inbounds--the-same-strings-the-copy-url-button-copies-in-the-panel-ui-supported-protocols-vmess-vless-trojan-shadowsocks-hysteria-if-streamsettingsexternalproxy-is-set-returns-one-url-per-external-proxy-protocols-without-a-url-form-socks-http-mixed-wireguard-dokodemo-tunnel-contribute-nothing - contents: [] + contents: + - content: >- + 服务端在字段被省略时自动填充;调用方提供的有效值不会被覆盖。若以已存在的 email 重新添加,且其已存储的 `subId` 一致,则沿用已存储的 `id`、`password`、`auth` 和 `secret`,而不是重新生成,以保证同一身份在其各个入站之间保持一致。 + + + - **VLESS / VMess** —— `id`,新生成的 UUID + + - **Trojan** —— `password` + + - **Shadowsocks** —— `password`。在 `2022-blake3-*` 入站上,若调用方提供的 password 经 base64 解码后的长度不等于该加密方式所需的密钥长度(16 或 32 字节),它会被替换为服务端生成的密钥,且调用仍然返回成功;因此若不打算交由服务端生成,请回读该客户端确认。传统加密方式则保留任何非空 password + + - **Hysteria** —— `auth` + + - **mtproto** —— `secret`,由该入站的伪装域名派生的 FakeTLS 密钥;该入站未设置伪装域名时,则取自 `www.cloudflare.com` + + - **WireGuard** —— 两个密钥都为空时生成 `privateKey` 与 `publicKey`;只提供了 `privateKey` 时仅推导 `publicKey`。此外还会分配 `allowedIPs`:从该入站现有对端所在的 /24 中取一个空闲的 `/32`,若该入站尚无对端,则取自 `10.0.0.0/24` + + + 同一请求体也接受、但服务端不会自动生成的字段:`preSharedKey` 与 `keepAlive`(WireGuard)、`adTag`(mtproto)。 + + + 其中只有 WireGuard 这一步可能失败。分配地址时会先把搜索范围扩大到所属的 /16,之后才以 `wireguard: no free address available in ` 放弃;而调用方自行提供的 `allowedIPs` 只做校验、不做分配:当同一入站上的另一个客户端已占用该地址时,返回 `wireguard: allowedIPs entry already used by another client:
`。该校验按入站进行,因此同一地址出现在两个不同入站上是允许的。POST /panel/api/clients/{email}/attach 也执行同样的校验——已带有地址的客户端会把该地址带入新的入站。 + heading: create-a-new-client-and-attach-it-to-one-or-more-inbounds-in-a-single-call-body-is-json-per-protocol-secrets-are-generated-server-side-when-omitted-so-callers-can-send-only-the-universal-fields + - content: >- + WireGuard 客户端会把已存储的 `allowedIPs` 带入新入站,而不是获得新分配的地址;因此当目标入站上的另一个客户端已占用该地址时,调用会以 `wireguard: allowedIPs entry already used by another client:
` 失败。请先在该入站上释放该地址——完整规则见 POST /panel/api/clients/add。 + heading: attach-an-existing-client-to-one-or-more-additional-inbounds-body-is-json --- {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} diff --git a/docs/public/openapi.json b/docs/public/openapi.json index 8f144a0cd..5a05aa7e5 100644 --- a/docs/public/openapi.json +++ b/docs/public/openapi.json @@ -4976,8 +4976,9 @@ "tags": [ "Clients" ], - "summary": "Create a new client and attach it to one or more inbounds in a single call. Body is JSON. Per-protocol secrets (UUID for VLESS/VMess, password for Trojan/Shadowsocks, auth for Hysteria) are generated server-side when omitted, so callers can send only the universal fields.", + "summary": "Create a new client and attach it to one or more inbounds in a single call. Body is JSON. Per-protocol secrets are generated server-side when omitted, so callers can send only the universal fields.", "operationId": "post_panel_api_clients_add", + "description": "Fields the server fills in when they are omitted — a valid value sent by the caller is never overwritten. Re-adding an email that already exists, with its stored `subId`, reuses the stored `id`, `password`, `auth` and `secret` instead of minting new ones, so the identity stays in sync across its inbounds.\n\n- **VLESS / VMess** — `id`, a fresh UUID\n- **Trojan** — `password`\n- **Shadowsocks** — `password`. On a `2022-blake3-*` inbound a supplied password that does not base64-decode to the key length of the cipher (16 or 32 bytes) is replaced by a generated key and the call still succeeds, so read the client back if you did not let the server pick. Legacy ciphers keep any non-empty password\n- **Hysteria** — `auth`\n- **mtproto** — `secret`, a FakeTLS secret derived from the fronting domain of the inbound, or from `www.cloudflare.com` when it has none\n- **WireGuard** — `privateKey` and `publicKey` when both are blank, or `publicKey` alone when only a `privateKey` was sent, plus `allowedIPs`: one free `/32` taken from the /24 the existing peers of that inbound already sit in, or from `10.0.0.0/24` when it has none\n\nAccepted on the same body but never generated: `preSharedKey` and `keepAlive` (WireGuard), `adTag` (mtproto).\n\nWireGuard is the only one of these that can fail. Allocation widens the search to the containing /16 before giving up with `wireguard: no free address available in `, and an `allowedIPs` supplied by the caller is validated instead of allocated: `wireguard: allowedIPs entry already used by another client:
` when a different client of that same inbound already holds it. The check is per inbound, so the same address on two different inbounds is accepted. The same validation runs on POST /panel/api/clients/{email}/attach, where a client that already carries an address brings it along.", "requestBody": { "required": true, "content": { @@ -5152,6 +5153,7 @@ ], "summary": "Attach an existing client to one or more additional inbounds. Body is JSON.", "operationId": "post_panel_api_clients_email_attach", + "description": "A WireGuard client brings its stored `allowedIPs` into the new inbound instead of being given a fresh address, so the call fails with `wireguard: allowedIPs entry already used by another client:
` when a different client of the target inbound already holds it. Free the address on that inbound first — see POST /panel/api/clients/add for the full rule.", "parameters": [ { "name": "email", diff --git a/frontend/public/openapi.json b/frontend/public/openapi.json index 909f57286..44a843b2b 100644 --- a/frontend/public/openapi.json +++ b/frontend/public/openapi.json @@ -6245,8 +6245,9 @@ "tags": [ "Clients" ], - "summary": "Create a new client and attach it to one or more inbounds in a single call. Body is JSON. Per-protocol secrets (UUID for VLESS/VMess, password for Trojan/Shadowsocks, auth for Hysteria) are generated server-side when omitted, so callers can send only the universal fields.", + "summary": "Create a new client and attach it to one or more inbounds in a single call. Body is JSON. Per-protocol secrets are generated server-side when omitted, so callers can send only the universal fields.", "operationId": "post_panel_api_clients_add", + "description": "Fields the server fills in when they are omitted — a valid value sent by the caller is never overwritten. Re-adding an email that already exists, with its stored `subId`, reuses the stored `id`, `password`, `auth` and `secret` instead of minting new ones, so the identity stays in sync across its inbounds.\n\n- **VLESS / VMess** — `id`, a fresh UUID\n- **Trojan** — `password`\n- **Shadowsocks** — `password`. On a `2022-blake3-*` inbound a supplied password that does not base64-decode to the key length of the cipher (16 or 32 bytes) is replaced by a generated key and the call still succeeds, so read the client back if you did not let the server pick. Legacy ciphers keep any non-empty password\n- **Hysteria** — `auth`\n- **mtproto** — `secret`, a FakeTLS secret derived from the fronting domain of the inbound, or from `www.cloudflare.com` when it has none\n- **WireGuard** — `privateKey` and `publicKey` when both are blank, or `publicKey` alone when only a `privateKey` was sent, plus `allowedIPs`: one free `/32` taken from the /24 the existing peers of that inbound already sit in, or from `10.0.0.0/24` when it has none\n\nAccepted on the same body but never generated: `preSharedKey` and `keepAlive` (WireGuard), `adTag` (mtproto).\n\nWireGuard is the only one of these that can fail. Allocation widens the search to the containing /16 before giving up with `wireguard: no free address available in `, and an `allowedIPs` supplied by the caller is validated instead of allocated: `wireguard: allowedIPs entry already used by another client:
` when a different client of that same inbound already holds it. The check is per inbound, so the same address on two different inbounds is accepted. The same validation runs on POST /panel/api/clients/{email}/attach, where a client that already carries an address brings it along.", "requestBody": { "required": true, "content": { @@ -6423,6 +6424,7 @@ ], "summary": "Attach an existing client to one or more additional inbounds. Body is JSON.", "operationId": "post_panel_api_clients_email_attach", + "description": "A WireGuard client brings its stored `allowedIPs` into the new inbound instead of being given a fresh address, so the call fails with `wireguard: allowedIPs entry already used by another client:
` when a different client of the target inbound already holds it. Free the address on that inbound first — see POST /panel/api/clients/add for the full rule.", "parameters": [ { "name": "email", diff --git a/frontend/src/pages/api-docs/endpoints.ts b/frontend/src/pages/api-docs/endpoints.ts index 9ca1bad98..b2d58ce69 100644 --- a/frontend/src/pages/api-docs/endpoints.ts +++ b/frontend/src/pages/api-docs/endpoints.ts @@ -844,13 +844,15 @@ export const sections: readonly Section[] = [ method: 'POST', path: '/panel/api/clients/add', summary: - 'Create a new client and attach it to one or more inbounds in a single call. Body is JSON. Per-protocol secrets (UUID for VLESS/VMess, password for Trojan/Shadowsocks, auth for Hysteria) are generated server-side when omitted, so callers can send only the universal fields.', + 'Create a new client and attach it to one or more inbounds in a single call. Body is JSON. Per-protocol secrets are generated server-side when omitted, so callers can send only the universal fields.', + description: + 'Fields the server fills in when they are omitted — a valid value sent by the caller is never overwritten. Re-adding an email that already exists, with its stored `subId`, reuses the stored `id`, `password`, `auth` and `secret` instead of minting new ones, so the identity stays in sync across its inbounds.\n\n- **VLESS / VMess** — `id`, a fresh UUID\n- **Trojan** — `password`\n- **Shadowsocks** — `password`. On a `2022-blake3-*` inbound a supplied password that does not base64-decode to the key length of the cipher (16 or 32 bytes) is replaced by a generated key and the call still succeeds, so read the client back if you did not let the server pick. Legacy ciphers keep any non-empty password\n- **Hysteria** — `auth`\n- **mtproto** — `secret`, a FakeTLS secret derived from the fronting domain of the inbound, or from `www.cloudflare.com` when it has none\n- **WireGuard** — `privateKey` and `publicKey` when both are blank, or `publicKey` alone when only a `privateKey` was sent, plus `allowedIPs`: one free `/32` taken from the /24 the existing peers of that inbound already sit in, or from `10.0.0.0/24` when it has none\n\nAccepted on the same body but never generated: `preSharedKey` and `keepAlive` (WireGuard), `adTag` (mtproto).\n\nWireGuard is the only one of these that can fail. Allocation widens the search to the containing /16 before giving up with `wireguard: no free address available in `, and an `allowedIPs` supplied by the caller is validated instead of allocated: `wireguard: allowedIPs entry already used by another client:
` when a different client of that same inbound already holds it. The check is per inbound, so the same address on two different inbounds is accepted. The same validation runs on POST /panel/api/clients/{email}/attach, where a client that already carries an address brings it along.', params: [ { name: 'client', in: 'body (json)', type: 'object', - desc: 'Client fields: email, subId, id (uuid), password, auth, flow, totalGB, expiryTime, limitIp, limitHwid, tgId (numeric Telegram user ID, 0 = none), comment, enable.', + desc: 'Client fields: email, subId, id (uuid), password, auth, flow, totalGB, expiryTime, limitIp, limitHwid, tgId (numeric Telegram user ID, 0 = none), comment, enable. Protocol-specific: secret and adTag (mtproto), privateKey, publicKey, preSharedKey, allowedIPs and keepAlive (WireGuard).', }, { name: 'inboundIds', @@ -898,6 +900,8 @@ export const sections: readonly Section[] = [ method: 'POST', path: '/panel/api/clients/:email/attach', summary: 'Attach an existing client to one or more additional inbounds. Body is JSON.', + description: + 'A WireGuard client brings its stored `allowedIPs` into the new inbound instead of being given a fresh address, so the call fails with `wireguard: allowedIPs entry already used by another client:
` when a different client of the target inbound already holds it. Free the address on that inbound first — see POST /panel/api/clients/add for the full rule.', params: [ { name: 'email', in: 'path', type: 'string', desc: 'Client email (unique identifier).' }, {