IP Transit API v1.0.0
Programmatically manage your Shulker IP Transit services. List and inspect your IPs, monitor live tunnel traffic, pull billing history, rename services, and generate one-time install / remove commands — all through a single REST endpoint secured by your existing Shulker API keys.
Introduction
The IP Transit API shares the same API key infrastructure as the Shulker API v2. A key only gains access to this API if its permissions JSON contains an ip_transit array — configured from the API Keys dashboard. No separate credentials are required.
https://shulker.in/api/ip_transit-v1.0/
📡 Single endpoint — every request goes to the same URL. The operation is selected with the action query parameter (defaults to info). Parameters may be sent as a query string, application/x-www-form-urlencoded, or a JSON body.
Authentication
All requests require an active API key. Three transports are accepted — the header methods are strongly recommended because query strings end up in proxy and server logs.
X-API-Key: sk_1e14aecda1a06bc22062f8fcb8e90cbde309d29d1e7955378b1aeaf6c130cdc3
Authorization: Bearer sk_1e14aecda1a06bc22062f8fcb8e90cbde309d29d1e7955378b1aeaf6c130cdc3
?api_key=sk_1e14aecda1a06bc22062f8fcb8e90cbde309d29d1e7955378b1aeaf6c130cdc3
Key validation rules
| Check | Behaviour |
|---|---|
| is_active | Key must exist and be active. Otherwise 401 invalid_api_key. |
| expires_at | If the key has an expiry in the past it is auto-deactivated and 401 api_key_expired is returned. |
| ip_restrictions | If the key has CIDR/range restrictions, the caller IP must match one of them or 403 ip_not_allowed. |
| permissions.ip_transit | Must be an array containing the required permission or "all". Otherwise 403 insufficient_permissions. |
🔐 Security Note — API keys grant control over live network attachments. Treat them like passwords. Never commit keys to version control. Use environment variables or a secret manager, restrict keys by IP where possible, and rotate them every 90 days.
Permissions System
IP Transit permissions live in their own namespace inside the key's permissions JSON, under the ip_transit key. Granting "all" enables every action. The dashboard writes this array for you.
{
"read": [],
"write": [],
"ip_transit": ["list", "view", "monitor", "history"]
}
| Permission | Grants access to |
|---|---|
| list | List all IPs on the account |
| view | View details of a single IP |
| monitor | Live online/offline status and traffic metrics |
| alias | Rename (set the alias of) an IP |
| install_command | Generate one-time install / remove commands |
| history | View billing and renewal history |
| all | All IP Transit permissions (superset) |
ℹ️ No permission required — info and permissions are always available to any key that can authenticate, so you can use them to discover what a key is allowed to do.
Rate Limits
Limits are stored per key and enforced across three sliding windows. Defaults are shown — a key may have tighter custom limits configured in the dashboard.
⚠️ Stricter limit on install_command — generating install/remove commands is capped at 6 requests per minute per key, independent of the general limits, because the commands are sensitive. Exceeding it returns 429 rate_limited with Retry-After: 60.
X-RateLimit-Remaining-Minute: 47 X-Request-Id: req_9f2c1a7b3d4e5f60 Retry-After: 60 # only present on 429
"_meta": { "request_id": "req_9f2c1a7b3d4e5f60", "timestamp": "2026-01-07T16:28:03+00:00", "response_time": "0.0412s", "version": "1.0.0", "rate_limit": { "minute_remaining": 47, "hour_remaining": 982, "day_remaining": 9961 } }
Error Handling
Every response — success or failure — is JSON and carries a success boolean and a _meta object with a request ID. Failed requests additionally carry error, error_code, and code.
HTTP status codes
| Status | Meaning |
|---|---|
| 200 OK | Request successful |
| 400 Bad Request | Invalid or missing parameters |
| 401 Unauthorized | Missing, invalid, or expired API key |
| 403 Forbidden | Insufficient permissions, IP restriction, suspended or expired IP |
| 404 Not Found | Unknown action, or IP not found on this account |
| 405 Method Not Allowed | Wrong HTTP verb for the action (see Allow header) |
| 429 Too Many Requests | Rate limit exceeded |
| 500 Internal Server Error | Unexpected server error |
| 502 Bad Gateway | Upstream tunnel backend unavailable |
Error codes
| error_code | HTTP | When it happens |
|---|---|---|
| missing_api_key | 401 | No key found in header, bearer token, or query string |
| invalid_api_key | 401 | Key does not exist or is inactive |
| api_key_expired | 401 | Key expiry has passed (key is auto-deactivated) |
| ip_not_allowed | 403 | Caller IP does not match the key's IP restrictions |
| insufficient_permissions | 403 | Key lacks the required ip_transit permission |
| ip_suspended | 403 | Install command requested for a suspended IP |
| ip_expired | 403 | Install command requested for an expired IP |
| validation_error | 400 | Bad id, ip, status, or alias |
| not_found | 404 | IP does not exist on this account |
| unknown_action | 404 | Action not in the route table (response lists valid actions) |
| method_not_allowed | 405 | Correct action but wrong HTTP method |
| rate_limited | 429 | Any rate limit exceeded |
| upstream_unavailable | 502 | Tunnel backend could not be reached |
| internal_error | 500 | Unhandled server exception |
{
"success": false,
"error": "This API key does not have permission for this action.",
"error_code": "insufficient_permissions",
"code": 403,
"required_permission": "monitor",
"granted_permissions": ["list", "view"],
"_meta": {
"request_id": "req_9f2c1a7b3d4e5f60",
"timestamp": "2026-01-07T16:28:03+00:00",
"response_time": "0.0021s",
"version": "1.0.0",
"rate_limit": {
"minute_remaining": 59,
"hour_remaining": 999,
"day_remaining": 9999
}
}
}
🆔 Debugging — always log _meta.request_id together with the status code. Support can trace the exact request, key, and response using that ID. When contacting support, include the request ID and the timestamp.
IP Status & Lifecycle
Every IP returned by the API has a computed status. The rules match the dashboard exactly, so both always agree.
| Status | Condition |
|---|---|
| active | Not suspended, not expired, and more than 7 days of runtime remaining |
| expiring | Not suspended, not expired, and 7 days or fewer remaining |
| expired | expires_at is in the past. A 3-day grace period is kept for the owner; grace_ends_at is populated. |
| suspended | Suspended either in the database or upstream on the tunnel backend. suspension_reason may be present. |
🕒 Grace period — an expired IP is kept on the account for 3 days (grace_ends_at). After that it is released. You cannot generate install commands for expired IPs — renew first.
📡 Live data cache — the upstream tunnel list is cached server-side for 4 seconds to protect the upstream provider's 120 req/min budget. Rapid polling within that window returns the same figures — it does not violate your own rate limits.
API Info & Permissions
Discover the API version, the capabilities of the calling key, its rate limits, and the full list of actions. No permission required — any authenticated key may call these.
curl "https://shulker.in/api/ip_transit-v1.0/?action=info" \ -H "X-API-Key: sk_..."
{
"success": true,
"api": "Shulker IP Transit API",
"version": "1.0.0",
"documentation": "/docs/ip-transit/",
"key": {
"name": "Monitoring Bot",
"expires_at": "2026-04-01T00:00:00Z",
"rate_limits": { "per_minute": 30, "per_hour": 500, "per_day": 5000 }
},
"granted_permissions": ["list", "view", "monitor"],
"all_access": false,
"available_permissions": {
"list": "List all IPs on the account",
"view": "View details of a single IP",
"monitor": "Live online/offline status and traffic",
"alias": "Rename (set alias of) IPs",
"install_command": "Generate one-time install / remove commands",
"history": "View billing and renewal history",
"all": "All IP Transit permissions"
},
"actions": {
"info": { "permission": null, "methods": ["GET"] },
"list": { "permission": "list", "methods": ["GET"] },
"get": { "permission": "view", "methods": ["GET"] },
"monitor": { "permission": "monitor", "methods": ["GET"] },
"history": { "permission": "history", "methods": ["GET"] },
"set_alias": { "permission": "alias", "methods": ["POST", "PUT", "PATCH"] },
"install_command": { "permission": "install_command", "methods": ["POST"] }
},
"_meta": { "request_id": "req_...", "version": "1.0.0" }
}💡 Tip — ?action=permissions is an alias of info and returns the exact same payload. Use it if you prefer a self-describing URL.
List IPs
Returns every active IP on the account with a status summary. The live block is included only if the key has the monitor permission; otherwise it is omitted.
| Parameter | Type | Description |
|---|---|---|
| action | string | list |
| status | string | Optional. Filter by active, expiring, expired, or suspended. |
curl "https://shulker.in/api/ip_transit-v1.0/?action=list" \ -H "X-API-Key: sk_..." # with a filter curl "https://shulker.in/api/ip_transit-v1.0/?action=list&status=expiring" \ -H "X-API-Key: sk_..."
{
"success": true,
"count": 2,
"summary": {
"total": 2,
"active": 1,
"expiring": 1,
"expired": 0,
"suspended": 0
},
"ips": [
{
"id": 42,
"ip": "203.0.113.10",
"alias": "Edge Frankfurt",
"region": "de0",
"status": "active",
"suspended": false,
"suspension_reason": null,
"created_at": "2025-11-02T09:14:00Z",
"expires_at": "2026-02-02T09:14:00Z",
"last_billed_at": "2026-01-02T09:14:00Z",
"days_left": 26,
"grace_ends_at": null,
"live": { "online": true, "rx_mbps": 12.482, "tx_mbps": 3.117 }
},
{
"id": 41,
"ip": "198.51.100.7",
"alias": null,
"region": "us1",
"status": "expiring",
"suspended": false,
"suspension_reason": null,
"created_at": "2025-12-08T18:02:11Z",
"expires_at": "2026-01-13T18:02:11Z",
"last_billed_at": "2026-01-06T18:02:11Z",
"days_left": 6,
"grace_ends_at": null,
"live": { "online": false, "rx_mbps": 0, "tx_mbps": 0 }
}
],
"_meta": { "request_id": "req_...", "version": "1.0.0" }
}ℹ️ Note — summary always reflects all IPs regardless of the status filter. Use it to build dashboards without a second request.
Get Single IP
Fetch one IP by id or by its public ip address. Always scoped to the key's owner — IPs belonging to other accounts return 404.
| Parameter | Type | Description |
|---|---|---|
| id | integer | Either this or ip. The service ID. |
| ip | string | Either this or id. The public IPv4 address. |
curl "https://shulker.in/api/ip_transit-v1.0/?action=get&id=42" \ -H "X-API-Key: sk_..." # or by IP curl "https://shulker.in/api/ip_transit-v1.0/?action=get&ip=203.0.113.10" \ -H "X-API-Key: sk_..."
{
"success": true,
"ip": {
"id": 42,
"ip": "203.0.113.10",
"alias": "Edge Frankfurt",
"region": "de0",
"status": "active",
"suspended": false,
"suspension_reason": null,
"created_at": "2025-11-02T09:14:00Z",
"expires_at": "2026-02-02T09:14:00Z",
"last_billed_at": "2026-01-02T09:14:00Z",
"days_left": 26,
"grace_ends_at": null,
"live": { "online": true, "rx_mbps": 12.482, "tx_mbps": 3.117 }
},
"_meta": { "request_id": "req_...", "version": "1.0.0" }
}⚠️ Validation — id must be numeric and ip must be a valid IP address, otherwise 400 validation_error. Passing both is allowed; id takes precedence.
Live Monitoring
Returns live tunnel state and traffic. Call it with id/ip for a single IP (full detail including subnets and byte counters), or without for a compact snapshot of every IP on the account.
| Parameter | Type | Description |
|---|---|---|
| id | integer | Optional. Single-IP mode. |
| ip | string | Optional. Single-IP mode by address. |
curl "https://shulker.in/api/ip_transit-v1.0/?action=monitor&id=42" \ -H "X-API-Key: sk_..."
{
"success": true,
"ip": { "id": 42, "ip": "203.0.113.10", "alias": "Edge Frankfurt", "status": "active" },
"live": {
"online": true,
"protocol": "wireguard",
"tunnel_ip": "10.64.0.2",
"rx_mbps": 12.482,
"tx_mbps": 3.117,
"rx_bytes": 1849203817,
"tx_bytes": 482019337,
"subnets": [
{ "kind": "ipv4", "cidr": "203.0.113.0/28" }
]
},
"_meta": { "request_id": "req_...", "version": "1.0.0" }
}curl "https://shulker.in/api/ip_transit-v1.0/?action=monitor" \ -H "X-API-Key: sk_..."
{
"success": true,
"count": 2,
"ips": [
{
"id": 42,
"ip": "203.0.113.10",
"alias": "Edge Frankfurt",
"status": "active",
"online": true,
"rx_mbps": 12.482,
"tx_mbps": 3.117
},
{
"id": 41,
"ip": "198.51.100.7",
"alias": null,
"status": "expiring",
"online": false,
"rx_mbps": 0,
"tx_mbps": 0
}
],
"_meta": { "request_id": "req_...", "version": "1.0.0" }
}📉 Upstream failures — if the tunnel backend cannot be reached, the API returns 502 upstream_unavailable rather than stale or partial data. Retry with a short backoff.
Billing History
Returns billing and renewal history for the account, newest first. Optionally scoped to a single IP.
| Parameter | Type | Description |
|---|---|---|
| id / ip | int / string | Optional. Scope history to a single IP. |
| limit | integer | Optional. Number of records, default 50, max 200. |
curl "https://shulker.in/api/ip_transit-v1.0/?action=history&id=42&limit=20" \ -H "X-API-Key: sk_..."
{
"success": true,
"count": 1,
"history": [
{
"ip_id": 42,
"ip": "203.0.113.10",
"type": "renew",
"billing_period": "1_month",
"months": 1,
"amount_usd": 4.5,
"period_start": "2026-01-02T09:14:00Z",
"period_end": "2026-02-02T09:14:00Z",
"created_at": "2026-01-02T09:14:22Z"
}
],
"_meta": { "request_id": "req_...", "version": "1.0.0" }
}Set Alias
Rename an IP with a human-friendly alias. Send an empty string to clear an existing alias. Accepts POST, PUT, or PATCH.
| Parameter | Type | Description |
|---|---|---|
| id / ip | int / string | Required. Which IP to rename. |
| alias | string | Required. Up to 40 chars: letters, numbers, spaces, -, _, .. Empty string clears it. |
curl -X POST https://shulker.in/api/ip_transit-v1.0/ \ -H "X-API-Key: sk_..." \ -H "Content-Type: application/json" \ -d '{ "action": "set_alias", "id": 42, "alias": "Edge Frankfurt" }'
{
"success": true,
"id": 42,
"ip": "203.0.113.10",
"alias": "Edge Frankfurt",
"_meta": { "request_id": "req_...", "version": "1.0.0" }
}📝 Audit trail — alias changes are recorded in the IP Transit event log with the key ID that made the change, so you can always trace who renamed what.
Install / Remove Command
Generates a one-time install command and a matching remove command for attaching the IP to your own server. The command is single-use and expires — treat it like a password.
| Parameter | Type | Description |
|---|---|---|
| id / ip | int / string | Required. Target IP. |
| ttl_hours | integer | Optional. Command lifetime, default 24, range 1–168. Alias: ttl. |
curl -X POST https://shulker.in/api/ip_transit-v1.0/ \ -H "X-API-Key: sk_..." \ -H "Content-Type: application/json" \ -d '{ "action": "install_command", "id": 42, "ttl_hours": 24 }'
{
"success": true,
"ip": { "id": 42, "ip": "203.0.113.10" },
"install_command": "curl -fsSL https://shulker.in/cdn/ip-transit/install.sh | sudo bash -s -- --token ipt_7f3a...",
"remove_command": "curl -fsSL https://shulker.in/cdn/ip-transit/install.sh | sudo bash -s -- --remove --token ipt_7f3a...",
"expires_at": "2026-01-08T16:28:03Z",
"ttl_hours": 24,
"warning": "This command works once and anyone who has it can attach this IP to their server. Treat it like a password.",
"_meta": { "request_id": "req_...", "version": "1.0.0" }
}⚠️ Sensitive — install commands are rate-limited to 6 per minute per key. They cannot be generated for suspended or expired IPs (403 ip_suspended / 403 ip_expired). The response is never stored in the API usage log — it is redacted as [redacted: contains a one-time install command].
🔐 Treat as a secret — anyone holding the install command can attach the IP to their own server. Deliver it over a secure channel, use it immediately, and never paste it into public channels or logs.
Response Objects
IP object
| Field | Type | Description |
|---|---|---|
| id | integer | Service ID — use it as id= in subsequent calls |
| ip | string | Public IPv4 address |
| alias | string|null | Human-friendly name, or null when unset |
| region | string | Node/region code (e.g. de0, us1) |
| status | string | active, expiring, expired, or suspended |
| suspended | boolean | True if suspended in DB or upstream |
| suspension_reason | string|null | Reason, when present |
| created_at | string | ISO 8601 creation time |
| expires_at | string|null | ISO 8601 expiry time |
| last_billed_at | string|null | ISO 8601 last billing time |
| days_left | integer|null | Days until expiry (negative when past) |
| grace_ends_at | string|null | End of the 3-day grace window, only when expired |
| live | object | { online, rx_mbps, tx_mbps } — omitted without monitor permission |
Live tunnel object
| Field | Type | Description |
|---|---|---|
| online | boolean | Tunnel is currently up |
| protocol | string | Tunnel protocol (e.g. wireguard) |
| tunnel_ip | string | Peer IP of the tunnel |
| rx_mbps | number | Current receive throughput, Mbps |
| tx_mbps | number | Current transmit throughput, Mbps |
| rx_bytes | integer | Bytes received this period |
| tx_bytes | integer | Bytes transmitted this period |
| subnets | array | [{ kind, cidr }] routed subnets |
History object
| Field | Type | Description |
|---|---|---|
| ip_id | integer | Service ID the record belongs to |
| ip | string | Public IP address |
| type | string | Billing action (e.g. renew) |
| billing_period | string | Tenure label (e.g. 1_month) |
| months | integer | Number of months billed |
| amount_usd | number | Amount charged, USD |
| period_start | string|null | ISO 8601 period start |
| period_end | string|null | ISO 8601 period end |
| created_at | string | ISO 8601 record creation |
_meta object
| Field | Type | Description |
|---|---|---|
| request_id | string | Unique request identifier — quote it to support |
| timestamp | string | ISO 8601 response time (UTC) |
| response_time | string | Server processing time, e.g. 0.0412s |
| version | string | API version (1.0.0) |
| rate_limit | object | { minute_remaining, hour_remaining, day_remaining } |
Code Examples
import os import requests from typing import Optional, Dict, Any class IPTransitAPI: def __init__(self, api_key: str, base_url: str = "https://shulker.in/api/ip_transit-v1.0/"): self.api_key = api_key self.base_url = base_url self.session = requests.Session() self.session.headers.update({ "X-API-Key": api_key, "Accept": "application/json", }) def _request(self, params: Dict[str, Any], method: str = "GET") -> Dict[str, Any]: if method.upper() == "GET": response = self.session.get(self.base_url, params=params) else: response = self.session.request(method, self.base_url, json=params) return response.json() def info(self) -> Dict[str, Any]: return self._request({"action": "info"}) def list_ips(self, status: Optional[str] = None) -> Dict[str, Any]: params = {"action": "list"} if status: params["status"] = status return self._request(params) def get_ip(self, ip_id: Optional[int] = None, ip: Optional[str] = None) -> Dict[str, Any]: params = {"action": "get"} if ip_id is not None: params["id"] = ip_id elif ip: params["ip"] = ip return self._request(params) def monitor(self, ip_id: Optional[int] = None, ip: Optional[str] = None) -> Dict[str, Any]: params = {"action": "monitor"} if ip_id is not None: params["id"] = ip_id elif ip: params["ip"] = ip return self._request(params) def history(self, ip_id: Optional[int] = None, limit: int = 50) -> Dict[str, Any]: params = {"action": "history", "limit": limit} if ip_id is not None: params["id"] = ip_id return self._request(params) def set_alias(self, ip_id: int, alias: str) -> Dict[str, Any]: return self._request( {"action": "set_alias", "id": ip_id, "alias": alias}, method="POST", ) def install_command(self, ip_id: int, ttl_hours: int = 24) -> Dict[str, Any]: return self._request( {"action": "install_command", "id": ip_id, "ttl_hours": ttl_hours}, method="POST", ) # Usage if __name__ == "__main__": client = IPTransitAPI(os.environ["SHULKER_API_KEY"]) # Discover what this key can do info = client.info() if not info["success"]: raise SystemExit(info["error"]) print(f"Key: {info['key']['name']} — perms: {info['granted_permissions']}") # List every IP, summarised listing = client.list_ips() for entry in listing["ips"]: print(f"{entry['ip']:16} {entry['status']:10} {entry['days_left']}d left") # Alert on anything not healthy for entry in listing["ips"]: if entry["status"] in ("expiring", "expired", "suspended"): print(f"WARN: {entry['ip']} is {entry['status']}") # Live traffic snapshot live = client.monitor() for entry in live.get("ips", []): print(f"{entry['ip']} online={entry['online']} rx={entry['rx_mbps']}Mbps tx={entry['tx_mbps']}Mbps")
class IPTransitAPI { constructor(apiKey, baseUrl = 'https://shulker.in/api/ip_transit-v1.0/') { this.apiKey = apiKey; this.baseUrl = baseUrl; } async request(params, method = 'GET') { const url = new URL(this.baseUrl); if (method === 'GET') { url.search = new URLSearchParams(params).toString(); const res = await fetch(url, { headers: { 'X-API-Key': this.apiKey } }); return res.json(); } const res = await fetch(url, { method, headers: { 'X-API-Key': this.apiKey, 'Content-Type': 'application/json', }, body: JSON.stringify(params), }); return res.json(); } info() { return this.request({ action: 'info' }); } listIPs(status) { return this.request({ action: 'list', ...(status ? { status } : {}) }); } getIP(id) { return this.request({ action: 'get', id }); } monitor(id) { return this.request({ action: 'monitor', ...(id ? { id } : {}) }); } history(id, limit=50) { return this.request({ action: 'history', id, limit }); } setAlias(id, alias) { return this.request({ action: 'set_alias', id, alias }, 'POST'); } installCommand(id, ttlHours = 24) { return this.request({ action: 'install_command', id, ttl_hours: ttlHours }, 'POST'); } } // Usage async function main() { const client = new IPTransitAPI(process.env.SHULKER_API_KEY); const info = await client.info(); if (!info.success) throw new Error(info.error); console.log(`Key ${info.key.name} → ${info.granted_permissions.join(', ')}`); const listing = await client.listIPs(); listing.ips.forEach(ip => { console.log(`${ip.ip} ${ip.status} ${ip.days_left}d left`); }); // Rename an IP const renamed = await client.setAlias(42, 'Edge Frankfurt'); console.log(`Alias set to: ${renamed.alias}`); // Live snapshot const live = await client.monitor(); live.ips.forEach(ip => { console.log(`${ip.ip} online=${ip.online} rx=${ip.rx_mbps} tx=${ip.tx_mbps}`); }); } main().catch(err => { console.error(err); process.exit(1); });
#!/usr/bin/env bash set -euo pipefail API_KEY="${SHULKER_API_KEY:?set SHULKER_API_KEY first}" BASE="https://shulker.in/api/ip_transit-v1.0/" IP_ID="42" api() { # $1 = query string (no leading ?) curl -fsS "${BASE}?$1" -H "X-API-Key: ${API_KEY}" } api_post() { # $1 = JSON body curl -fsS -X POST "${BASE}" \ -H "X-API-Key: ${API_KEY}" \ -H "Content-Type: application/json" \ -d "$1" } echo "→ Listing IPs" api "action=list" | jq -r ' "Total: \(.summary.total) active: \(.summary.active) expiring: \(.summary.expiring)", (.ips[] | " \(.ip)\t\(.status)\t\(.days_left)d left")' echo "→ Live status for IP #${IP_ID}" api "action=monitor&id=${IP_ID}" | jq -r ' "online=\(.live.online) rx=\(.live.rx_mbps)Mbps tx=\(.live.tx_mbps)Mbps"' echo "→ Generating a fresh install command (TTL 1h)" INSTALL=$(api_post "{ \"action\": \"install_command\", \"id\": ${IP_ID}, \"ttl_hours\": 1 }" | jq -r '.install_command') # Run it on the server that should announce the IP. echo "Run on your server:" echo " ${INSTALL}"
Best Practices
🔐 Security — keep API keys in environment variables or a secret manager, restrict keys with IP allow-lists, grant only the ip_transit permissions you need (e.g. monitoring bots rarely need install_command), and rotate keys every 90 days.
⚡ Polling — the tunnel list is cached server-side for 4 seconds, so polling monitor more frequently than once every ~5 seconds adds no information. For dashboards, poll once per 10–15 s and reuse the response; for alerting, prefer checking only status=expiring and status=suspended.
🔄 Retries — retry 429 and 502 with exponential backoff plus jitter, honouring the Retry-After header on 429. Do not retry 400, 401, 403, or 404 — those are deterministic and will keep failing.
🧾 Idempotency — install_command and set_alias accept POST, PUT, and PATCH where applicable. Because install commands are single-use and rate-limited to 6/min, never auto-retry them blindly — generate a new one instead.
🆔 Observability — log _meta.request_id alongside the status code for every call. It is the fastest way for support to trace a specific request on our side.
Changelog
v1.0.0 — Initial release — Public launch of the Shulker IP Transit API. Reuses existing Shulker API keys with a dedicated ip_transit permission namespace. Added info, list, get, monitor, history, set_alias, and install_command actions. Live tunnel data sourced from the upstream backend with a 4-second server-side cache. Per-key sliding-window rate limits (minute / hour / day) plus a stricter 6/min cap on install commands. Install command responses are redacted from the API usage log. IP filtering by status with an always-complete summary block.
ℹ️ Compatibility — the IP Transit API shares authentication with Shulker API v2. Keys without an ip_transit permission will authenticate successfully but receive 403 insufficient_permissions on every action except info / permissions. Enable the permission from the API Keys dashboard.