For operators
This is the contract the iOS app keeps with a panel. If you run the server side and give people an address or a share link, this is what the app will request, what it will accept, and what it will refuse. It is not a list of providers. How a configuration typically reaches someone is on where to get servers.
How a configuration gets in
iOS registers one URL type. A tap on a registered scheme opens the app, and the whole URL is imported. There is no path or host router. The schemes, exact strings:
tunelly,
vless,
vmess,
trojan,
ss,
hysteria2,
hy2,
tuic,
wireguard.
vless, hysteria2, hy2,
wireguard and tunelly are how a
working configuration arrives.
vmess, trojan, ss and
tuic open the app only so it can refuse them
cleanly — they are not supported.
socks, socks5, http and
https are understood by the parser but are not
registered, so a tap on such a link does not open the app.
The same text can also arrive from the welcome screen's one-tap clipboard paste, from Add a server, or by typing into the field. A subscriber-facing walkthrough is on how to add a configuration.
Classification, in order: a sealed envelope is checked first.
Then a whole wg-quick file. Then the rest of the
text. A bare panel.example.com/sub/abc is rewritten
to https:// and fetched.
https://host:port#name and
http://user:pass@host are treated as share proxies,
not fetched — fetching those would lose the server.
The subscription fetch
This is the part a panel has to get right. The app issues a
GET over http or https
only. The session is ephemeral. The request times out at twenty
seconds, the resource at forty. A cached copy is ignored.
Request headers
- User-Agent
- One of the identities below, in that order.
- Accept
*/*- x-hwid
- The iOS vendor identifier when one is present. It is not a hardware identifier. It is stable for this app on this device and resets when the app is removed.
- x-device-os
ios, sent withx-hwid.
Identity walk
Panels already branch on a handful of well-known names and hand back different documents for each. We ask in those dialects so the panel returns a list we can read. The names are the contract. The version numbers move with releases; the strings below belong to the current build.
- 1. Tunelly — this build sends
Tunelly/0.1 - 2. Happ — this build sends
Happ/1.64.0 - 3. v2rayNG — this build sends
v2rayNG/1.9.0 - 4. ClashMeta — this build sends
ClashMeta/1.18.0 - 5. SFA — this build sends
SFA/1.10.0
HTTP 401, 403 and 404 stop the walk: those answers concern the address, not the identity. Another HTTP status, or a body this identity cannot read, continues to the next name.
The body
The body is sniffed. Content-Type is not used —
panels serve base64 as text/html and JSON as
text/plain often enough that the header is not
evidence.
Accepted:
- a
wg-quickfile - a JSON array of Xray configurations
- a JSON object with
outbounds -
JSON wrappers keyed
links,servers,configs,dataorsubscription - a plain list of links
- base64, including double-encoded
Refused by name:
- an HTML page
- Clash YAML
- Clash JSON
Those documents are parsed here. The traffic is carried by our own engine, not passed through to another core.
Response metadata
Read from headers or from #name: value lines in
the body. When both speak, the body wins. The filename in
Content-Disposition is not read.
- profile-title
-
Display name. A
base64:prefix is decoded. There is no character cap. - subscription-userinfo
-
upload,download,total,expire. Every field is optional. - profile-update-interval
- Hours. A panel that means daily writes
24. - profile-web-page-url
- A page the operator wants shown.
- support-url
- Where the subscriber can ask you, not us.
- announce
- A free-text notice. A
base64:prefix is decoded. - announce-url
- A link that belongs with the notice.
- new-url
- A whole new fetch address. Shown to the person. Never applied silently.
- new-domain
-
A new host; the path — the credential — stays. Same
confirmation as
new-url. - fallback-url
- Where to go if the stored address stops answering.
- update-always
-
trueor1treats the subscription as stale whenever the app opens. The person's own refresh switch still wins.
Protocols the engine runs
VLESS, Hysteria2, WireGuard (including AmneziaWG), and nothing else. A configuration for any other protocol is refused at import, so a row that carries no traffic never exists. The protocol pages hold the parameter tables.
Two corrections operators get wrong. The Hysteria2 password is
the URI userinfo; an auth query parameter is not
read. spx / SpiderX is parsed but not honoured by
the engine — do not present it as working.
Hiding the address
A sealed carrier hides the panel address from a subscriber
using the app. The URI form is
tunelly://e/<ver>/<kid>/<payload>.
The file form is
tunelly-seal/1 <kid> <payload>.
Three kinds: a subscription address, a single share link, or a
body snapshot. A sealed envelope is checked before every other
import branch.
This hides the address from a subscriber using the app. It is not a secret from someone who inspects the binary, and it is not a secret from someone who watches the device's traffic — the device has to send packets to the server. Treat it as a deterrent against casual copying, not as a substitute for access control on your panel. The keys are not a secret embedded in the binary in any stronger sense than that.
The browser tool is on seal a subscription. We do not open other clients' encrypted link formats.
What will not work
A configuration the engine cannot honour is refused at import, with the reason:
- unimplemented protocols — VMess, Trojan, Shadowsocks, TUIC, SOCKS, HTTP
- Clash bodies, YAML or JSON
- HTML responses
- Hysteria2 obfuscation and port hopping
- WARP reserved bytes
- gRPC multi-mode
- KCP
- TCP header types other than none or http
- XHTTP and HTTP/2 without TLS underneath
- empty credentials
An operator arriving from another client will also look for things that are not here. Stated so nobody builds against imagined behaviour:
- QR import
- universal links
- a share extension
- document types
- a manual per-field editor
- install caps
- device-locked links
- a provider account
- forced app settings
- routing deep links
- geo-file downloads
- JSON passed through to a third-party core
- ping modes
- a TV API
Where a server comes from
We do not operate servers and we do not sell access. Where to get servers is how a configuration typically reaches an application of this kind, without naming anyone to buy from.