Skip to content

Check a config without a key

POST
/check/public
curl --request POST \
--url https://app.tunnelhq.com/api/v1/check/public \
--header 'Content-Type: application/json' \
--data '{ "url": "vless://<uuid>@sg-01.your-vpn.net:443?type=tcp&security=reality&sni=www.microsoft.com&pbk=<public-key>#sg-01" }'

The free tester: tests one config from Any location, with no key. Limited to 5 requests per IP address per 24 hours, and every request counts, including ones refused for a bad config. Nothing is saved.

The fields are those of Create monitor: a share link in url (or config), a config file in config, and host, username and password where the protocol needs them. OpenVPN needs username and password here too. The test may take 30 seconds. Private, loopback and cloud-metadata addresses are refused.

Media typeapplication/json
object
url

A share link.

string
config

A share link or a config file.

string
protocol

Required unless the share link’s scheme gives it.

string
Allowed values: wireguard amneziawg openvpn openconnect ikev2 vless vmess trojan shadowsocks hysteria2 tuic
host
string
port
integer
proto
string
Allowed values: udp tcp
username
string
password
string
remote_id
string
engine

The engine for share links. Left out, the checker chooses.

string
Allowed values: singbox xray auto both race
Example
{
"url": "vless://<uuid>@sg-01.your-vpn.net:443?type=tcp&security=reality&sni=www.microsoft.com&pbk=<public-key>#sg-01"
}

The test ran.

Media typeapplication/json
object
test_id
required

An ID for this check. It can’t be looked up.

string
status
required
string
Allowed values: healthy down
protocol
required
string
Allowed values: wireguard amneziawg openvpn openconnect ikev2 vless vmess trojan shadowsocks hysteria2 tuic
config_detected
required

true when the protocol came from a share link.

boolean
tested_at
required

When the check started, in ISO 8601 UTC.

string
latency_ms
required
integer | null
connection_time_ms
required
integer | null
handshake_time_ms
required
integer | null
assigned_ip
required

When the tunnel came up: its own address if it has one (WireGuard, IKEv2), otherwise the public address traffic left from (share links).

string | null
error_message
required

The checker’s own description of the failure. The wording varies.

string | null
error_category
required

The kind of failure, such as server_not_connected, internet_stuck, timeout, auth_failed, cert_error, or config_error.

string | null
engine_used
required

The engine that ran a share-link check: xray or singbox.

string | null
remaining_checks
required

Checks this IP address has left in the 24 hours.

integer
Example
{
"test_id": "chk_muh251s09f41c07d2e",
"status": "healthy",
"protocol": "vless",
"config_detected": true,
"tested_at": "2026-09-25T14:30:00.000Z",
"latency_ms": 162,
"connection_time_ms": 410,
"handshake_time_ms": 120,
"assigned_ip": "198.51.100.20",
"error_message": null,
"error_category": null,
"engine_used": "xray",
"remaining_checks": 4
}

The protocol or engine is missing or unknown.

Media typeapplication/json
object
error
required
boolean
code
required

The HTTP status, repeated.

integer
message
required

What went wrong.

string
details

More detail, on some errors.

retry_after

Seconds to wait, on the 429 for wrong keys.

integer
Examples
{
"error": true,
"code": 400,
"message": "Missing 'protocol' field. Supported: vless, vmess, trojan, shadowsocks, hysteria2, tuic, wireguard, amneziawg, openvpn, openconnect, ikev2."
}

A configuration field is longer than its limit.

Media typeapplication/json
object
error
required
boolean
code
required

The HTTP status, repeated.

integer
message
required

What went wrong.

string
details

More detail, on some errors.

retry_after

Seconds to wait, on the 429 for wrong keys.

integer
Example
{
"error": true,
"code": 413,
"message": "Field 'password' exceeds max length (4096 bytes). Got 5000 bytes."
}

The config can’t be used, or its host isn’t allowed.

Media typeapplication/json
object
error
required
boolean
code
required

The HTTP status, repeated.

integer
message
required

What went wrong.

string
details

More detail, on some errors.

retry_after

Seconds to wait, on the 429 for wrong keys.

integer
Examples
{
"error": true,
"code": 422,
"message": "OpenVPN requires 'username' and 'password' (even if the config has auth-user-pass, credentials must be provided)."
}

This IP address has used its 5 checks for the last 24 hours.

Media typeapplication/json
object
error
required
string
remaining
required
integer
0
retry_after
required

Seconds.

integer
Example
{
"error": "Rate limit exceeded. Max 5 checks per IP per 24 hours. Sign up for unlimited access.",
"remaining": 0,
"retry_after": 86400
}

TunnelHQ’s checker couldn’t be reached.

Media typeapplication/json
object
error
required
boolean
code
required

The HTTP status, repeated.

integer
message
required

What went wrong.

string
details

More detail, on some errors.

retry_after

Seconds to wait, on the 429 for wrong keys.

integer
Example
{
"error": true,
"code": 502,
"message": "VPN checker unreachable: timeout of 35000ms exceeded"
}

No checker is available.

Media typeapplication/json
object
error
required
boolean
code
required

The HTTP status, repeated.

integer
message
required

What went wrong.

string
details

More detail, on some errors.

retry_after

Seconds to wait, on the 429 for wrong keys.

integer
Example
{
"error": true,
"code": 503,
"message": "No VPN checker nodes available. Try again shortly."
}