> For the complete documentation index, see [llms.txt](https://docs.tumbler.app/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.tumbler.app/subscription-contract/response-formats.md).

# Response formats

## Who this is for

Developers who already built an add link and now want to verify what exactly the subscription URL should return.

## What you can do

* Keep your existing format if it is already supported.
* Understand which formats are parsed strictly and which are best-effort.
* Pass subscription metadata in the body or HTTP headers.

## See also

* [getting-started/quickstart.md](/getting-started/quickstart.md)
* [getting-started/add-link.md](/getting-started/add-link.md)
* [subscription/metadata.md](/subscription-contract/metadata.md) - supported meta fields.
* [operations/caching-and-304.md](/after-first-launch/caching-and-304.md)
* [features/basic-limits.md](/features/basic-limits.md)

## Supported body formats

Tumbler does not require a special new format if the provider already has a working VLESS/Xray/sing-box subscription. Supported responses are listed below. The first five are parsed strictly; Xray and sing-box are parsed **best-effort**. An empty body or the absence of valid VLESS nodes results in an error.

After parsing, the backend converts the provider response into its internal normalized snapshot and then delivers an encrypted runtime payload to the mobile client. Providers should rely on the public input contract documented here: response body formats, the `meta` object or meta lines, and supported HTTP headers.

### 1) Plain text `vless://...` (strict)

The body is a line-by-line list of VLESS links.

Example:

```
vless://11111111-1111-1111-1111-111111111111@edge.example.com:443?security=reality&pbk=pk1&sid=s1&sni=edge.example.com#Edge-1
vless://22222222-2222-2222-2222-222222222222@ws.example.com:80?type=ws&path=%2Fws#WS
```

* **Node source:** each non-empty line starting with `vless://`.
* **Extracted fields:** server, port, UUID; query `security`, `pbk`, `sid`, `sni`, `fp`, `type/net`; `#fragment`/`remarks/ps` as the name; all other query values go into `node.meta`.
* **Common errors:** missing port/UUID, unsupported scheme, port outside 1-65535, malformed UUID.

### 2) Mixed text (strict)

VLESS lines mixed with metadata lines `#key: value` or `#key=value`.

Example:

```
#support-url: https://help.example.com
#hide-settings = true
#mirrors: https://m1.example.com/sub, https://m2.example.com/sub
#subscription-userinfo: upload=123456; download=654321; total=10737418240; expire=1800000000
vless://aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa@node1.example.com:443?security=reality&pbk=pkA&sid=01&sni=node1.example.com#Reality-1
vless://bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb@node2.example.com:2053?type=grpc#GRPC
```

* **Node source:** only `vless://...` lines; empty and other lines are ignored.
* **Metadata:** meta lines are normalized to keys from [metadata.md](/subscription-contract/metadata.md); validation rules:
  * `mirrors`: comma-separated string or array; spaces around commas are ignored; only absolute `http/https` URLs without userinfo are allowed (`https://user:pass@example.com/sub` is not allowed because credentials leak into logs, referrers, and error messages).
  * `installation-limit` / `connection-limit`: non-negative integers.
* **Common errors:** no valid VLESS link remains after filtering; mirrors with userinfo in the URL or without a scheme; negative limits.

### 3) Base64 subscription (strict)

If the body looks like Base64 (std or raw) and the decoded content looks like a subscription, it is decoded and parsed as one of the text/JSON formats.

Example body (decodes to one VLESS link):

```
dmxlc3M6Ly8xMTExMTExMS0yMjIyLTMzMzMtNDQ0NC01NTU1NTU1NTU1NTVAZWRnZS5leGFtcGxlLmNvbTo0NDM/c2VjdXJpdHk9cmVhbGl0eSZwYms9QWJDZEVmJnNpZD0xMjM0JnNuaT1lZGdlLmV4YW1wbGUuY29tI0VkZ2UtMQ==
```

* **Node source:** from the decoded result; then the same parser is applied as for plain/mixed text lists, JSON arrays/objects, or JSON configs (Xray/sing-box).
  * First we try to parse JSON (`links`/`nodes` or `outbounds` for Xray/sing-box); on JSON decoding failure we fall back to a text list.
  * Base64 decoding is performed once; nested layers are not expanded.
* **Common errors:**
  * The body decodes, but contains no valid VLESS link or JSON array/object.
  * After decoding, the string is empty or does not look like a subscription.

### 4) JSON array of subscription items

The array may contain:

* `vless://...` strings;
* objects with `server/port/uuid`;
* Xray/sing-box config objects with `outbounds` (best-effort extraction of `vless` outbounds from each item).

Example:

```json
[
  "vless://aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa@a.example.com:443#A",
  { "server": "b.example.com", "port": 8443, "uuid": "bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb", "transport": "ws" },
  { "outbounds": [{ "type": "vless", "server": "c.example.com", "server_port": 2053, "uuid": "cccccccc-cccc-cccc-cccc-cccccccccccc" }] }
]
```

* **Node source:** each VLESS string, each object with `server`, `port`, `uuid`, and `vless` outbounds from config objects.
* **Common errors:** empty array; items contain no valid `vless://`, no valid `server/port/uuid`, and no valid `vless` outbounds.

### 5) JSON object `{ links/meta }` (strict)

The `links` key (or `nodes`) contains an array of links or objects; `meta` contains metadata.

Example:

```json
{
  "links": [
    "vless://cccccccc-cccc-cccc-cccc-cccccccccccc@c.example.com:443#C"
  ],
  "meta": {
    "support-url": "https://help.example.com",
    "new-url": "https://new.example.com/sub",
    "connection-limit": 2,
    "subscription-userinfo": "upload=123456; download=654321; total=10737418240; expire=1800000000"
  }
}
```

* **Node source:** the `links` array (fallback: `nodes`) with the same rules as a JSON array.
* **Metadata:** read from `meta`; override order is described in "General VLESS/Reality extraction rules" below.
* **Common errors:** `links`/`nodes` is not an array or contains no valid VLESS; invalid URL in mirrors/new-url/new-domain; negative limits.

### 6) Xray JSON (best-effort)

Outbounds with `type/protocol: "vless"` are parsed. This works for typical Xray configs.

Supported as a single JSON object and as objects inside a top-level JSON array.

Example:

```json
{
  "outbounds": [
    {
      "type": "vless",
      "tag": "edge-xray",
      "settings": {
        "vnext": [
          {
            "address": "edge.example.com",
            "port": 443,
            "users": [{ "id": "99999999-9999-9999-9999-999999999999", "flow": "xtls-rprx-vision" }]
          }
        ]
      },
      "streamSettings": {
        "network": "grpc",
        "security": "reality",
        "realitySettings": { "publicKey": "pk-xray", "shortId": "ab12", "serverName": "edge.example.com", "fingerprint": "chrome" }
      },
      "meta": { "group": "xray" }
    }
  ]
}
```

* **Node source and fields:**
  * `outbounds[].settings.vnext[].{address/port/users[].id}`; the legacy shape `outbounds[].vnext[]` directly inside the outbound is also supported.
  * Name: `tag` or `email/remark`.
  * `streamSettings`: `network` -> `transport`, `security` -> `security`.
  * `realitySettings`: `publicKey/pbk`, `shortId/sid/short_id`, `serverName/sni`, `fingerprint/fp`.
  * `outbound.meta` is merged into `node.meta`.
* **Common errors:** missing `users` or `address/port`; `outbounds` is not an array; all outbounds have a different `type`; Reality parameters outside `streamSettings` are ignored.

### 7) sing-box JSON (best-effort)

The parser looks for `outbounds[].type == "vless"` with required `server/server_port/uuid`.

Supported as a single JSON object and as objects inside a top-level JSON array.

Example:

```json
{
  "outbounds": [
    {
      "type": "vless",
      "name": "sb-reality",
      "server": "sb.example.com",
      "server_port": 8443,
      "uuid": "88888888-8888-8888-8888-888888888888",
      "transport": { "type": "ws", "path": "/ws" },
      "tls": {
        "server_name": "sb.example.com",
        "utls": { "fingerprint": "chrome" },
        "reality": { "public_key": "pk-sb", "short_id": "cd34" }
      },
      "meta": { "group": "sing-box" }
    }
  ]
}
```

* **Node source:** `server/server_port/uuid`; name from `name/remark/ps` or `tag`; `transport.type` -> `network`; `tls.reality` -> `security=reality`, `pbk`, `sid`; `tls.server_name` -> `sni`; `tls.utls.fingerprint` -> `fp`; `meta` is copied into `node.meta`.
* **Common errors:** one of required `server/port/uuid` is missing; `server_port` has an invalid type; all outbounds have another type.

## General VLESS/Reality extraction rules

* `server`, `port`, `uuid`, `security`, `transport`, `pbk`, `sid`, `sni`, `fp`, name, and `meta` are hashed; any change changes `version_id`.
* Additional query parameters or unknown object fields go into `node.meta`.
* Metadata is read from the body (`meta` object or meta lines), then overridden by HTTP headers (`X-Subscription-*`, `Support-URL`, `Hide-Settings`, and others); see [metadata.md](/subscription-contract/metadata.md) for keys and priority.
* Profile title supports both key forms: `profile-title` and `profile_title` (in headers and `meta`).
* Quota/expiry supports `subscription-userinfo` and direct keys `upload`/`download`/`total`/`expire` (`expire-at` alias).
* Content type: `application/json; charset=utf-8` by default; `text/plain; charset=utf-8` for text lists; use `Content-Encoding: gzip/zstd` only by agreement.


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs.tumbler.app/subscription-contract/response-formats.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
