docs: clarify subscription extension protocol

This commit is contained in:
Qiu Yuzhou
2026-10-26 00:49:38 +08:00
parent a84f9c4951
commit 56bae991d1
2 changed files with 149 additions and 67 deletions
+80 -39
View File
@@ -1,11 +1,9 @@
# SIP-008 Subscription Extensions: Provider Guide
# SIP-008 subscription extensions: provider guide
This guide describes the SIP-008 fields and private extensions currently supported by ShadowsocksX-NG. It is intended for subscription providers generating compatible responses. It reflects the current implementation.
Use this guide to build SIP-008 responses compatible with ShadowsocksX-NG. It covers the supported standard fields, private extensions, and parsing rules.
[简体中文](subscription-provider-extension.zh.md)
`x_shadowsocksx_ng` is the namespace for all ShadowsocksX-NG private extensions, including `expires_at` and `traffic_reset_at`. These are not SIP-008 standard fields. Future project-specific fields must also live inside this root-level namespace, rather than directly in the root or server objects. Ordinary SIP-008 clients may not recognize them, and clients without extension support should still read the servers from the standard `servers` array. SIP-008 allows custom fields but does not guarantee that all clients support them. See the [SIP-008 Online Configuration Delivery specification](https://shadowsocks.org/doc/sip008.html).
## Response delivery
The subscription endpoint must meet these requirements:
@@ -25,7 +23,7 @@ The root object must contain `version: 1` and a `servers` array. An empty array
| --- | --- | --- |
| `version` | Must be the integer `1`. | A missing or different version rejects the entire document. |
| `servers` | Must be an array. Each entry uses the SIP-008 server fields listed below. | A missing value or a non-array rejects the entire document. An empty array is valid. |
| `servers[].id` | Required by SIP-008. Give each server a unique, canonical UUID and preserve it across refreshes for the same logical server. The grouping extension uses this field to reference servers. | The current parser tolerates a missing `id`, but any supplied value must be a valid UUID. Duplicate IDs reject the entire document. Entries without an ID cannot be referenced by the extension tree and follow the flat compatibility path. |
| `servers[].id` | Required by SIP-008. Give each server a unique, canonical UUID and preserve it across refreshes for the same logical server. The grouping extension uses this field to reference servers. | The current parser tolerates a missing `id`, but any supplied value must be a valid UUID. Duplicate IDs reject the entire document. Entries without an ID cannot be referenced by the group tree and are handled as ungrouped servers. |
| `servers[].remarks` | Required by SIP-008. Provide a string to use as the server's display name. | The current parser tolerates omission; the name is empty when omitted. |
| `servers[].server` | Must be a non-empty string. | A missing or empty value rejects the entire document. |
| `servers[].server_port` | Must be an integer from `1` through `65535`. | A missing, non-integer, or out-of-range value rejects the entire document. |
@@ -35,24 +33,84 @@ The root object must contain `version: 1` and a `servers` array. An empty array
A server ID represents logical identity, not its array position. Preserve the ID when renaming a server, changing its address or port, or updating its credentials if it remains the same logical server. Assign a new ID to a new logical server. Use the canonical hyphenated UUID form, and keep the exact same string in the server entry and extension references.
## Extension namespace and version
All ShadowsocksX-NG private extensions belong in the root-level `x_shadowsocksx_ng` object. This includes expiration and traffic reset dates, automatic update settings, and the group tree. Future project-specific fields must also use this namespace, rather than being added directly to the document root or server records.
These fields are not part of SIP-008. Clients that do not recognize them must still be able to obtain every server from the standard `servers` array. For SIP-008's provisions on custom fields, see [SIP-008 Online Configuration Delivery](https://shadowsocks.org/doc/sip008.html).
| Field | Format and meaning |
| --- | --- |
| `x_shadowsocksx_ng` | A JSON object at the document root containing ShadowsocksX-NG private extensions. |
| `x_shadowsocksx_ng.schema_version` | Required integer, currently `1`. If the version is missing or unsupported, the client does not accept new extension data from this object. |
Dates, `auto_update`, and the group tree are optional and can be used in any combination. With a supported schema version, each is parsed independently. An invalid group tree does not prevent a valid `auto_update` value from being accepted; an invalid `auto_update` value does not invalidate groups or dates. The `auto_update` section below specifies how missing and invalid values affect previously accepted data.
## Subscription information
The following traffic fields are defined by SIP-008. Providers should follow SIP-008's rules for these fields. The client displays only values that match the formats below.
Use the SIP-008 standard fields for traffic usage and the private extension fields for dates. Values must use the following formats.
| Field | Provider format | Meaning |
| --- | --- | --- |
| `bytes_used` | A non-negative JSON integer from `0` through `18446744073709551615`. | Number of bytes used by the user. Do not send a string, negative number, decimal, or exponent form. |
| `bytes_remaining` | A non-negative JSON integer in the same range. Omit it when there is no data limit; include `bytes_used` as well when a limit is enforced. | Number of bytes remaining. Do not use a private field to repeat the same traffic meaning. |
| `bytes_remaining` | A non-negative JSON integer in the same range. Omit it when there is no data limit; include `bytes_used` as well when a limit is enforced. | Number of bytes remaining. Do not duplicate this information in a private field. |
| `x_shadowsocksx_ng.expires_at` | An RFC 3339 date-time string, such as `2027-01-01T00:00:00Z` or `2027-01-01T08:00:00+08:00`. | A ShadowsocksX-NG private field for the service expiration time. |
| `x_shadowsocksx_ng.traffic_reset_at` | An RFC 3339 date-time string. | A ShadowsocksX-NG private field for the traffic reset time. |
Date fields are direct members of `x_shadowsocksx_ng` and require `schema_version: 1`; the namespace may contain only the version and dates, without a group tree. Root-level `expires_at` and `traffic_reset_at` are ignored, with no fallback. A missing or non-object namespace, or a missing or unsupported schema version, provides no date information. The standard traffic fields remain at the root.
Date fields are direct members of `x_shadowsocksx_ng` and require `schema_version: 1`; the namespace may contain only the version and dates, without a group tree. Root-level `expires_at` and `traffic_reset_at` are ignored. A missing or non-object namespace, or a missing or unsupported schema version, provides no date information. The standard traffic fields remain at the root.
An omitted date field or `null` means no value is provided. The client parses these fields independently. An invalid value is ignored without discarding valid servers, groups, or other valid information. The client reads this information from the JSON body; it does not parse response headers such as `Subscription-Userinfo`.
## Private extension namespace: `x_shadowsocksx_ng`
## Automatic updates: `auto_update`
Add the private extension namespace to the root object. The example below includes dates and a nested group tree. Keep every server in `servers`; the group tree carries only group names, hierarchy, order, and server references. Dates, the group tree, and `auto_update` are parsed independently: invalid content in one feature does not discard valid content in another under a supported schema version.
The optional `x_shadowsocksx_ng.auto_update` object declares the provider's automatic update mode and whether users may override it. It does not require dates or a group tree.
| Field | Type | Meaning and constraints |
| --- | --- | --- |
| `auto_update.mode` | String | Required and case-sensitive. Accepts only `"off"` (disabled), `"daily"` (daily updates), or `"weekly"` (weekly updates). |
| `auto_update.allow_user_override` | Boolean | Required. When `true`, `mode` is a default that users may override. When `false`, the provider's `mode` must be used. |
Neither field has a default. For example, `mode: "off"` with `allow_user_override: true` disables automatic updates by default but allows users to enable them. With `allow_user_override: false`, it requires automatic updates to remain disabled. The protocol has no fields for custom intervals or fixed update times.
### Missing and invalid values
The entire `auto_update` object is invalid if either required field is missing, has the wrong type, or contains an unsupported `mode`. An empty object, `null`, an array, or a boolean is also invalid. The string `"true"` and the number `1` are not accepted as substitutes for the boolean `true`.
Once a response passes base-document validation and is successfully applied, the client handles `auto_update` as follows:
| Response content | Result |
| --- | --- |
| `schema_version: 1` with a complete, valid `auto_update` object | Accept the new value, replacing any previous value. |
| `schema_version: 1` with `auto_update` omitted | Clear the previous value. |
| `x_shadowsocksx_ng` omitted or set to `null` | Clear the previous value. |
| `auto_update` present but invalid, including `null` | Ignore the new value and retain the previous value. |
| A non-object, non-null namespace, or a missing, incorrectly typed, or unsupported `schema_version` | Ignore the new value and retain the previous value. |
To withdraw `auto_update`, omit the field. Sending `null` or an empty object will retain the previous value. An invalid `auto_update` value does not prevent valid servers, groups, or dates from being parsed.
## Group tree
The group tree defines names, hierarchy, display order, and server references. All fields in this section belong inside `x_shadowsocksx_ng`; server configurations stay in the root-level `servers` array.
| Field | Meaning and constraints |
| --- | --- |
| `root_group_id` | A non-empty string that must match the `id` of the root group in `groups`. |
| `groups` | An array of group objects. Each `id` must be non-empty and unique; a UUID that remains stable across refreshes is recommended. A group ID must not conflict with a server ID. |
| `groups[].name` | Group display name. Providing it is recommended; the client displays an empty name if it is omitted. |
| `groups[].children` | An ordered array. Its order is the display order in the client. Each entry must be `{ "type": "group", "id": "group ID" }` or `{ "type": "server", "id": "server UUID" }`. |
The groups must form an acyclic tree rooted at `root_group_id`: each group has at most one parent, and every group and server reference resolves to an object. Reference each server at most once, and place every group intended for display in the tree reachable from the root. Group and server IDs are stable identities; do not use names, array positions, or mutable configuration values as IDs.
Group parsing and fallback rules:
- If the extension version is missing or unsupported, the root group is missing, IDs conflict, a reference is invalid, a child type is unknown, a cycle exists, or a group has multiple parents, the client ignores the group tree and uses the flat standard `servers` list. Valid servers are not lost because of an invalid group tree.
- A server in `servers` that is not referenced by a valid tree is still shown at the end of the root group, in the order it appears in the `servers` array. If the extension is invalid or incomplete, servers may still appear, but their grouping is not preserved.
- Groups unreachable from the root are not shown. Do not rely on client behavior for unreachable groups or duplicate server references; providers should produce a valid tree without orphan groups or repeated server references.
- A record without a server `id` cannot be referenced by the tree. Provide a stable ID for every server to preserve grouping.
## Complete response example
This response includes two servers, traffic usage and dates, a daily update default that users may override, and nested groups.
```json
{
@@ -81,6 +139,10 @@ Add the private extension namespace to the root object. The example below includ
"schema_version": 1,
"expires_at": "2027-01-01T00:00:00Z",
"traffic_reset_at": "2026-11-01T00:00:00Z",
"auto_update": {
"mode": "daily",
"allow_user_override": true
},
"root_group_id": "f6ad0a41-2a1d-4db8-9da0-b6f4f3dc889d",
"groups": [
{
@@ -103,33 +165,12 @@ Add the private extension namespace to the root object. The example below includ
}
```
| Field | Meaning and constraints |
| --- | --- |
| `x_shadowsocksx_ng` | ShadowsocksX-NG private extension object. This field name is not an official SIP-008 field. |
| `schema_version` | The currently supported extension version must be the integer `1`. |
| `root_group_id` | A non-empty string that must match the `id` of the root group in `groups`. |
| `groups` | An array of group objects. Each `id` must be non-empty and unique; a UUID that remains stable across refreshes is recommended. A group ID must not conflict with a server ID. |
| `groups[].name` | Group display name. Providing it is recommended; the client displays an empty name if it is omitted. |
| `groups[].children` | An ordered array. Its order is the display order in the client. Each entry must be `{ "type": "group", "id": "group ID" }` or `{ "type": "server", "id": "server UUID" }`. |
## Provider checklist
The provider should make the extension a single-parent, acyclic tree rooted at `root_group_id`: each group has at most one parent, and every group and server reference resolves to an object. Reference each server at most once, and place every group intended for display in the tree reachable from the root. Group and server IDs are stable identities; do not use names, array positions, or mutable configuration values as IDs.
The client handles the extension as follows:
- If the extension version is missing or unsupported, the root group is missing, IDs conflict, a reference is invalid, a child type is unknown, a cycle exists, or a group has multiple parents, the client ignores the group tree and uses the flat standard `servers` list. Valid servers are not lost because of an invalid group tree.
- A server in `servers` that is not referenced by a valid tree is still shown at the end of the root group, in the order it appears in the `servers` array. If the extension is invalid or incomplete, servers may still appear, but their grouping is not preserved.
- Groups unreachable from the root are not shown. Do not rely on client behavior for unreachable groups or duplicate server references; providers should produce a valid tree without orphan groups or repeated server references.
- A record without a server `id` cannot be referenced by the tree. Provide a stable ID for every server to preserve grouping.
## Provider release checklist
- Use HTTPS and return HTTP `200` with `Content-Type: application/json; charset=utf-8`.
- Return valid JSON with root-level `version: 1` and complete server records in `servers`.
- Give every server a unique, stable UUID; use the exact same server ID in `servers` and group references.
- If using the extension, set `schema_version` to `1`. If providing a group tree, include the root group, resolve every reference, and keep the tree ordered, single-parent, and acyclic.
- Use the SIP-008 standard fields for traffic information; use RFC 3339 strings for expiration and reset dates inside `x_shadowsocksx_ng`.
- Verify that a standard SIP-008 client that does not support `x_shadowsocksx_ng` still receives all servers in the flat `servers` list.
For details about how the application presents subscription fields and handles refreshes, see [“Subscription information” in the configuration guide](configuration.md#subscription-information).
A successful refresh replaces subscription information; missing or invalid dates clear previous values. Fetch, document-validation, or save failures preserve the previous information. Existing locally saved dates remain until a successful refresh; no local storage migration is needed. Group exports include servers and grouping only, omitting subscription dates, traffic, and provider update policies. File imports create independent manual subtrees without retaining subscription information.
- Serve the response over HTTPS with HTTP status `200` and `Content-Type: application/json; charset=utf-8`.
- Return valid JSON with `version: 1` at the root and complete server records in `servers`.
- Assign each server a unique, stable UUID. Use exactly the same string in server records and group references.
- Set `schema_version` to `1` when using private extensions. If a group tree is included, ensure the root exists, all references resolve, and the tree is ordered, single-parent, and acyclic.
- Use the standard SIP-008 fields for traffic usage. Put expiration and reset dates inside `x_shadowsocksx_ng` as RFC 3339 strings.
- If `auto_update` is included, provide both a valid `mode` and a boolean `allow_user_override`. Check that the override setting matches your intent. To withdraw the value, omit it instead of sending `null` or an empty object.
- Check compatibility with a SIP-008 client that does not support `x_shadowsocksx_ng`: the flat `servers` array must still contain every server.
+69 -28
View File
@@ -4,8 +4,6 @@
[English](subscription-provider-extension.md)
`x_shadowsocksx_ng` 是 ShadowsocksX-NG 所有私有扩展的命名空间,包含 `expires_at`、`traffic_reset_at`,这些都不是 SIP-008 标准字段。今后本项目新增的扩展字段也必须归入根对象的这个命名空间,不直接放在根层或服务器对象中。普通 SIP-008 客户端不一定识别这些字段;没有扩展支持的客户端仍应从标准 `servers` 数组读取服务器。SIP-008 允许自定义字段,但不保证所有客户端支持。详见 [SIP-008 Online Configuration Delivery](https://shadowsocks.org/doc/sip008.html)。
## 响应传输
订阅端点必须满足以下条件:
@@ -35,9 +33,22 @@ SIP-008 订阅 URL 通常包含访问令牌。请使用不可猜测的令牌,
服务器 ID 表示逻辑身份,不是数组位置。改名、改地址、改端口或更新凭据时,如果仍是同一逻辑服务器,应保留原 ID;新增一台逻辑服务器时分配新 ID。UUID 应采用连字符分隔的规范形式,并在正文和扩展引用中保持完全相同的字符串。
## 私有扩展与版本
`x_shadowsocksx_ng` 是根对象中的私有扩展命名空间,当前支持 `schema_version: 1`。服务到期时间、流量重置时间、自动更新策略和分组树都放在该对象内。新增的项目私有字段也须归入这个命名空间,不直接放在根层或服务器对象中。
这些字段不是 SIP-008 标准字段。普通 SIP-008 客户端不一定识别它们,因此根层的 `servers` 仍须包含全部服务器。SIP-008 对自定义字段的说明见 [SIP-008 Online Configuration Delivery](https://shadowsocks.org/doc/sip008.html)。
| 字段 | 格式与含义 |
| --- | --- |
| `x_shadowsocksx_ng` | 根层的 JSON 对象,承载 ShadowsocksX-NG 私有扩展。 |
| `x_shadowsocksx_ng.schema_version` | 必须是整数 `1`。缺失或不支持的版本不会采用其中的新扩展数据。 |
时间资料、`auto_update` 和分组树可以单独提供,也可以组合使用。版本受支持时,各功能独立解析:分组树无效不会阻止有效更新策略生效,更新策略无效也不会丢弃有效分组或时间资料。`auto_update` 的缺失与无效值处理见下文。
## 订阅资料
以下流量字段来自 SIP-008。供应方应遵守 SIP-008 对数据量字段的约定;客户端仅把符合下表格式的值显示为订阅资料。
以下流量字段来自 SIP-008。供应方应遵守 SIP-008 对数据量字段的约定;字段格式如下。
| 字段 | 供应方格式 | 说明 |
| --- | --- | --- |
@@ -46,13 +57,60 @@ SIP-008 订阅 URL 通常包含访问令牌。请使用不可猜测的令牌,
| `x_shadowsocksx_ng.expires_at` | RFC 3339 日期时间字符串,例如 `2027-01-01T00:00:00Z` 或 `2027-01-01T08:00:00+08:00`。 | ShadowsocksX-NG 私有字段,表示服务到期时间。 |
| `x_shadowsocksx_ng.traffic_reset_at` | RFC 3339 日期时间字符串。 | ShadowsocksX-NG 私有字段,表示流量重置时间。 |
时间字段直接放在 `x_shadowsocksx_ng` 中,要求 `schema_version: 1`;该对象可以只包含版本和时间字段,无需提供分组树。根层的 `expires_at`、`traffic_reset_at` 不再读取,也不会作为兼容来源。命名空间缺失、不是对象,或版本缺失、不支持时,视为未提供时间资料。标准流量字段仍放在根层。
时间字段直接放在 `x_shadowsocksx_ng` 中,要求 `schema_version: 1`;该对象可以只包含版本和时间字段,无需提供分组树。根层的 `expires_at`、`traffic_reset_at` 不予解析。命名空间缺失、不是对象,或版本缺失、不支持时,视为未提供时间资料。标准流量字段仍放在根层。
日期字段缺失或为 `null` 表示未提供。客户端逐项解析这些资料;某一项格式错误只会忽略该项,不会丢弃有效服务器、分组或其他有效资料。客户端从 JSON 正文读取资料,不解析 HTTP `Subscription-Userinfo` 等响应头。
## 私有扩展命名空间 `x_shadowsocksx_ng`
## 自动更新 `auto_update`
在根对象中增加私有扩展命名空间。以下示例同时提供时间资料和嵌套分组树。`servers` 仍须包含全部服务器;分组树只表达分组名称、层级、顺序和服务器引用。时间、分组树与 `auto_update` 独立解析:版本受支持时,一个功能内容错误不影响其他有效功能。
`x_shadowsocksx_ng.auto_update` 是可选对象,用于声明供应方的自动更新模式及是否允许用户覆盖。该对象与时间资料、分组树互不依赖。
| 字段 | 类型 | 含义与约束 |
| --- | --- | --- |
| `auto_update.mode` | 字符串 | 必填,区分大小写。只接受 `"off"`(关闭)、`"daily"`(每天)或 `"weekly"`(每周)。 |
| `auto_update.allow_user_override` | 布尔值 | 必填。`true` 表示 `mode` 是允许用户覆盖的默认值;`false` 表示必须采用供应方的 `mode`。 |
两个字段均无缺省值。`mode: "off"` 与 `allow_user_override: true` 组合表示默认关闭,允许用户开启;与 `false` 组合表示强制关闭。协议不提供自定义更新间隔或固定更新时间字段。
### 缺失与无效值
缺少任意必填字段、字段类型错误或 `mode` 取值不受支持,都会使整个 `auto_update` 无效。`null`、空对象、数组、布尔值也都是无效值;字符串 `"true"` 和数字 `1` 不能代替布尔值 `true`。
对于通过基础文档校验并成功应用的响应,客户端按下表处理供应方的 `auto_update`。
| 响应内容 | 处理结果 |
| --- | --- |
| `schema_version: 1`,且 `auto_update` 完整有效 | 采用本次值,替换旧值。 |
| `schema_version: 1`,但省略 `auto_update` | 清除旧值。 |
| 省略整个 `x_shadowsocksx_ng`,或将其设为 `null` | 清除旧值。 |
| `auto_update` 存在但无效,包括设为 `null` | 忽略本次值,保留旧值。 |
| 命名空间不是对象且非 `null`,或 `schema_version` 缺失、类型错误、不受支持 | 忽略本次值,保留旧值。 |
因此,撤回 `auto_update` 时应省略该字段,不能用 `null` 或空对象代替。无效的 `auto_update` 不影响有效服务器、分组或时间资料的解析。
## 分组树
分组树表达分组名称、层级、顺序和服务器引用。以下字段均位于 `x_shadowsocksx_ng` 中,服务器配置仍放在根层 `servers`。
| 字段 | 含义和约束 |
| --- | --- |
| `root_group_id` | 非空字符串,必须对应 `groups` 中根分组的 `id`。 |
| `groups` | 分组对象数组。每个 `id` 必须非空且唯一;建议使用不随刷新变化的 UUID。分组 ID 不得与服务器 ID 冲突。 |
| `groups[].name` | 分组显示名称。建议始终提供;缺省时客户端显示空名称。 |
| `groups[].children` | 有序数组,数组顺序就是客户端显示顺序。每项必须是 `{ "type": "group", "id": "分组 ID" }` 或 `{ "type": "server", "id": "服务器 UUID" }`。 |
供应方应确保扩展构成从 `root_group_id` 出发的单父、无环树:每个分组最多有一个父分组,所有分组和服务器引用都能解析到相应对象。每个服务器最多引用一次;请将每个预期展示的分组放在根分组可到达的树中。分组与服务器 ID 都是稳定身份,不要把名称、数组下标或可变配置内容当作 ID。
分组树的解析与回退规则如下:
- 扩展版本缺失或不支持、根分组不存在、ID 冲突、引用无效、子项类型未知、出现环或分组被多个父分组引用时,分组树会被忽略,客户端改用标准 `servers` 扁平列表;有效服务器不会因此丢失。
- 未被有效树引用但存在于 `servers` 的服务器仍会显示在根分组末尾,顺序沿用其在 `servers` 数组中的顺序。因此扩展失效或不完整时,服务器仍可能显示,但分组关系不会保留。
- 树中不可从根到达的分组不会显示。请勿依赖客户端对不可达分组或重复服务器引用的处理;供应方应输出没有孤立分组、没有重复服务器引用的有效树。
- 缺少服务器 `id` 的记录无法被树引用;为保证分组准确,请给所有服务器提供稳定 ID。
## 完整响应示例
此示例包含两台服务器、时间与流量资料、允许用户覆盖的每天更新策略,以及嵌套分组。
```json
{
@@ -81,6 +139,10 @@ SIP-008 订阅 URL 通常包含访问令牌。请使用不可猜测的令牌,
"schema_version": 1,
"expires_at": "2027-01-01T00:00:00Z",
"traffic_reset_at": "2026-11-01T00:00:00Z",
"auto_update": {
"mode": "daily",
"allow_user_override": true
},
"root_group_id": "f6ad0a41-2a1d-4db8-9da0-b6f4f3dc889d",
"groups": [
{
@@ -103,24 +165,6 @@ SIP-008 订阅 URL 通常包含访问令牌。请使用不可猜测的令牌,
}
```
| 字段 | 含义和约束 |
| --- | --- |
| `x_shadowsocksx_ng` | ShadowsocksX-NG 私有扩展对象。该字段名不是 SIP-008 官方标准字段。 |
| `schema_version` | 当前支持的扩展版本必须是整数 `1`。 |
| `root_group_id` | 非空字符串,必须对应 `groups` 中根分组的 `id`。 |
| `groups` | 分组对象数组。每个 `id` 必须非空且唯一;建议使用不随刷新变化的 UUID。分组 ID 不得与服务器 ID 冲突。 |
| `groups[].name` | 分组显示名称。建议始终提供;缺省时客户端显示空名称。 |
| `groups[].children` | 有序数组,数组顺序就是客户端显示顺序。每项必须是 `{ "type": "group", "id": "分组 ID" }` 或 `{ "type": "server", "id": "服务器 UUID" }`。 |
供应方应确保扩展构成从 `root_group_id` 出发的单父、无环树:每个分组最多有一个父分组,所有分组和服务器引用都能解析到相应对象。每个服务器最多引用一次;请将每个预期展示的分组放在根分组可到达的树中。分组与服务器 ID 都是稳定身份,不要把名称、数组下标或可变配置内容当作 ID。
客户端的扩展处理行为如下:
- 扩展版本缺失或不支持、根分组不存在、ID 冲突、引用无效、子项类型未知、出现环或分组被多个父分组引用时,分组树会被忽略,客户端改用标准 `servers` 扁平列表;有效服务器不会因此丢失。
- 未被有效树引用但存在于 `servers` 的服务器仍会显示在根分组末尾,顺序沿用其在 `servers` 数组中的顺序。因此扩展失效或不完整时,服务器仍可能显示,但分组关系不会保留。
- 树中不可从根到达的分组不会显示。请勿依赖客户端对不可达分组或重复服务器引用的处理;供应方应输出没有孤立分组、没有重复服务器引用的有效树。
- 缺少服务器 `id` 的记录无法被树引用;为保证分组准确,请给所有服务器提供稳定 ID。
## 提供方发布前检查
- 使用 HTTPS,返回 HTTP `200` 和 `Content-Type: application/json; charset=utf-8`。
@@ -128,8 +172,5 @@ SIP-008 订阅 URL 通常包含访问令牌。请使用不可猜测的令牌,
- 为每台服务器提供唯一且稳定的 UUID;服务器 ID 在 `servers` 和分组引用中逐字一致。
- 若使用扩展,`schema_version` 为 `1`;若提供分组树,根分组须存在,所有引用有效,树有序、单父、无环。
- 流量字段使用 SIP-008 标准字段;到期与重置日期放在 `x_shadowsocksx_ng` 下,使用本文规定的 RFC 3339 字符串。
- 若提供 `auto_update`,同时提供有效的 `mode` 和布尔值 `allow_user_override`;确认是否允许用户覆盖,并检查撤回策略时没有误用 `null` 或空对象。
- 以不支持 `x_shadowsocksx_ng` 的普通 SIP-008 客户端验证时,扁平 `servers` 列表仍包含全部服务器。
订阅字段的应用内呈现与刷新行为见[配置文档中的“订阅资料”](configuration.zh.md#订阅资料)。
成功刷新会替换订阅资料,缺失或无效的时间字段清除对应旧值;获取、文档校验或保存失败则保留原资料。本地已保存的时间保留到下次成功刷新,无需迁移本地存储。配置组导出只携带服务器和分组,不携带订阅时间、流量或供应商更新策略;文件导入创建独立手动子树,不保留订阅资料。