main
md 274 lines 9.79 KB
Rendered Raw
1 ---
2 title: API Reference
3 description: Portal relay API contract, endpoint groups, auth, and shared response rules.
4 ---
5
6 # API Reference
7
8 Portal relay exposes one control-plane API. Frontends, SDK clients, peer relays,
9 and operators all talk to this API, but each group has a small owned surface.
10 Local agent endpoints under `/agent/*` are not part of the relay API.
11
12 ## JSON Envelope
13
14 Matched JSON control endpoints return this envelope:
15
16 ```json
17 {
18 "ok": true,
19 "data": {}
20 }
21 ```
22
23 Error responses use the same envelope with `error` instead of `data`:
24
25 ```json
26 {
27 "ok": false,
28 "error": {
29 "code": "unauthorized",
30 "message": "unauthorized"
31 }
32 }
33 ```
34
35 `data` is omitted on error. `error` is omitted on success.
36
37 The envelope does not apply to streaming or delegated endpoints:
38
39 | Path | Format |
40 |------|--------|
41 | `/sdk/connect` | HTTP/1.1 connection hijack |
42 | `/v1/sign` | keyless TLS signer protocol |
43 | `/api/x402/*` | relay-owned x402 facilitator response |
44 | `/api/install.sh`, `/api/install.ps1`, `/api/install/bin/*` | script or binary bytes |
45
46 Unknown routes may be handled by the frontend/proxy layer or return a normal
47 HTTP 404 outside the envelope.
48
49 ## Auth Schemes
50
51 | Name | Used by | How it is sent |
52 |------|---------|----------------|
53 | None | public and challenge endpoints | no credential |
54 | Admin bearer | admin API | `Authorization: Bearer <access_token>` |
55 | Lease token header | tunnel stream and keyless signer | `X-Portal-Access-Token: <access_token>` |
56 | Lease token body | lease renew/unregister | JSON field `access_token` |
57 | Signed descriptor | relay discovery announce | signed `RelayDescriptor` body |
58 | Signed hop route | relay overlay route | signed `HopRoute` body |
59
60 Admin auth and SDK lease auth issue different tokens and are not
61 interchangeable. SDK lease registration uses SIWE; relay admin access uses the
62 configured admin token.
63
64 ## Endpoint Groups
65
66 ### Public
67
68 | Method | Path | Auth | Response |
69 |--------|------|------|----------|
70 | `GET` | `/` | None | service identity |
71 | `GET` | `/api/healthz` | None | `{ "status": "ok" }` |
72 | `GET` | `/api/state` | None | `PublicStateResponse` |
73 | `GET`/`HEAD` | `/api/install.sh`, `/api/install.ps1` | None | install script |
74 | `GET`/`HEAD` | `/api/install/bin/{slug}` | None | install binary or redirect |
75
76 ### Frontend Presentation API
77
78 These paths are served by the TypeScript API service when the static frontend stack is
79 enabled. They live under `/ui/` and are derived from relay APIs plus frontend-owned
80 presentation state.
81
82 | Method | Path | Auth | Response |
83 |--------|------|------|----------|
84 | `GET` | `/ui/state` | None | `PublicStateResponse` plus `landing_page_enabled` |
85 | `GET` | `/ui/service/status?hostname=...` | None | `ServiceStatusResponse` |
86 | `GET` | `/ui/policy/state` | Admin bearer | `PolicyStateResponse` plus `landing_page_enabled` in `policy` |
87 | `GET`/`POST` | `/ui/policy` | Admin bearer | `PolicySettings` plus `landing_page_enabled` |
88 | `POST` | `/ui/policy/leases`, `/ui/policy/ips` | Admin bearer | relay policy update response |
89 | `GET` | `/ui/thumbnail/{hostname}` | None | generated image |
90
91 ### SDK
92
93 | Method | Path | Auth | Body | Response |
94 |--------|------|------|------|----------|
95 | `GET` | `/sdk/domain` | None | none | `DomainResponse` |
96 | `POST` | `/sdk/register/challenge` | None | `RegisterChallengeRequest` | `RegisterChallengeResponse` |
97 | `POST` | `/sdk/register` | SIWE signature body | `RegisterRequest` | `RegisterResponse` |
98 | `POST` | `/sdk/renew` | lease token body | `RenewRequest` | `RenewResponse` |
99 | `POST` | `/sdk/unregister` | lease token body | `UnregisterRequest` | `{}` |
100 | `GET` | `/sdk/connect` | lease token header | none | hijacked stream |
101
102 `/sdk/hop` is a relay-to-relay overlay route endpoint. It is not used by normal
103 SDK clients.
104
105 ### Admin
106
107 | Method | Path | Auth | Body | Response |
108 |--------|------|------|------|----------|
109 | `POST` | `/api/admin/auth/login` | None | `AdminAuthLoginRequest` | `AdminAuthLoginResponse` |
110 | `GET` | `/api/admin/auth/status` | Optional admin bearer | none | `AdminAuthStatusResponse` |
111 | `POST` | `/api/admin/auth/logout` | Admin bearer | none | `{}` |
112
113 `/admin` itself is a frontend route, not a relay API endpoint.
114
115 ### Payments
116
117 Relay `/api/x402/*` endpoints are optional relay-owned control-plane
118 facilitator endpoints. Enable them with `X402_ENABLED=true` when a relay
119 operator wants to reserve support for relay resources such as future tunnel
120 registration fees, lease renewal fees, raw TCP/UDP port allocation, or premium
121 capacity. They are served by the embedded `gosuda/x402-facilitator` handler and
122 do not use the Portal JSON envelope. Portal selects Sui mainnet by default and
123 Sui testnet when `X402_TESTNET=true`. Portal accepts only USDC gasless
124 stablecoin address-balance payments. `X402_PAY_TO` is the relay-owned payment
125 recipient.
126
127 Relay x402 settings do not affect tunnel paid routes. Tunnel payment recipients
128 and payment networks are local tunnel configuration and are not part of the
129 relay lease API.
130
131 Paid routed HTTP tunnels additionally expose `/x402/prepare` and
132 `/x402/client.js` on the public tunnel origin. Those are tunnel-owned helper
133 endpoints for app frontends, not relay API routes, and they do not use the
134 `/api` prefix. Tunnel paid routes use Sui mainnet by default and Sui testnet
135 when the tunnel is exposed with `--x402-testnet` or configured with
136 `x402_testnet = true`. `/x402/client.js` is browser-only; native clients call
137 `/x402/prepare` directly and send `X-PAYMENT` on the protected request.
138
139 | Method | Path | Auth | Body | Response |
140 |--------|------|------|------|----------|
141 | `GET` | `/api/x402/supported` | None | none | x402 supported kinds |
142 | `POST` | `/api/x402/verify` | None | x402 verify request | x402 verify response |
143 | `POST` | `/api/x402/settle` | None | x402 settle request | x402 settle response |
144
145 ### Policy
146
147 | Method | Path | Auth | Body | Response |
148 |--------|------|------|------|----------|
149 | `GET` | `/api/policy` | Admin bearer | none | `PolicySettings` |
150 | `POST` | `/api/policy` | Admin bearer | `PolicySettings` | `PolicySettings` |
151 | `GET` | `/api/policy/state` | Admin bearer | none | `PolicyStateResponse` |
152 | `POST` | `/api/policy/leases` | Admin bearer | `LeasePolicyUpdate` | `{}` |
153 | `POST` | `/api/policy/ips` | Admin bearer | `IPPolicyUpdate` | `{}` |
154
155 ### Relay
156
157 | Method | Path | Auth | Response |
158 |--------|------|------|----------|
159 | `GET` | `/discovery` | None | `DiscoveryResponse` |
160 | `POST` | `/discovery/announce` | Signed descriptor | `DiscoveryAnnounceResponse` |
161 | `POST` | `/v1/sign` | Lease token header | keyless signer response |
162
163 ## Shared Types
164
165 Timestamps are JSON-encoded Go `time.Time` values.
166
167 `Identity`:
168
169 | Field | Type | Notes |
170 |-------|------|-------|
171 | `name` | `string` | DNS label used by the lease |
172 | `address` | `string` | Ethereum address |
173
174 `LeaseMetadata`:
175
176 | Field | Type | Notes |
177 |-------|------|-------|
178 | `description` | `string` | optional |
179 | `owner` | `string` | optional |
180 | `thumbnail` | `string` | optional URL or data value |
181 | `tags` | `string[]` | optional |
182 | `hide` | `boolean` | hidden leases are omitted from the public state |
183
184 `Lease`:
185
186 | Field | Type |
187 |-------|------|
188 | `name` | `string` |
189 | `expires_at`, `first_seen_at`, `last_seen_at` | `string` |
190 | `hostname` | `string` |
191 | `udp_enabled`, `tcp_enabled` | `boolean` |
192 | `tcp_addr` | `string` |
193 | `metadata` | `LeaseMetadata` |
194 | `ready` | `number` |
195
196 `PolicyLease` extends `Lease` with:
197
198 | Field | Type |
199 |-------|------|
200 | `identity_key`, `address` | `string` |
201 | `bps` | `number` |
202 | `client_ip`, `reported_ip` | `string` |
203 | `is_approved`, `is_banned`, `is_denied`, `is_ip_banned` | `boolean` |
204
205 `ServiceStatusResponse`:
206
207 | Field | Type |
208 |-------|------|
209 | `hostname` | `string` |
210 | `registered` | `boolean` |
211 | `service_alive` | `boolean` |
212
213 `PolicyStateResponse`:
214
215 | Field | Type |
216 |-------|------|
217 | `policy` | `PolicySettings` |
218 | `leases` | `PolicyLease[]` |
219
220 `PolicyPortSettings`:
221
222 | Field | Type | Notes |
223 |-------|------|-------|
224 | `enabled` | `boolean` | enables the transport |
225 | `max_leases` | `number` | `0` means unlimited |
226
227 `PolicySettings`:
228
229 | Field | Type |
230 |-------|------|
231 | `approval_mode` | `"auto"` or `"manual"` |
232 | `udp` | `PolicyPortSettings` |
233 | `tcp_port` | `PolicyPortSettings` |
234
235 `LeasePolicyUpdate`:
236
237 | Field | Type | Notes |
238 |-------|------|-------|
239 | `identity_key` | `string` | normalized `name:address` key |
240 | `is_banned` | `boolean` | optional |
241 | `is_approved` | `boolean` | optional |
242 | `is_denied` | `boolean` | optional; `true` also revokes approval |
243 | `bps` | `number` | optional; `0` removes the limit |
244
245 `IPPolicyUpdate`:
246
247 | Field | Type |
248 |-------|------|
249 | `ip` | `string` |
250 | `is_banned` | `boolean` |
251
252 ## Common Errors
253
254 | Code | Meaning |
255 |------|---------|
256 | `invalid_json` | request body is not valid JSON |
257 | `invalid_request` | request shape or value is invalid |
258 | `method_not_allowed` | endpoint does not accept the method |
259 | `unauthorized` | credential is missing, expired, or invalid |
260 | `feature_unavailable` | feature is disabled or not configured |
261 | `rate_limited` | request was throttled |
262 | `hostname_conflict` | lease hostname is already registered |
263 | `lease_not_found` | lease token or identity has no active lease |
264 | `lease_rejected` | lease is not currently allowed to route |
265 | `ip_banned` | source or reported IP is banned |
266 | `invalid_address` | address path or body value is invalid |
267 | `invalid_ip` | IP path value is invalid |
268 | `invalid_mode` | approval mode is not `auto` or `manual` |
269 | `http11_only` | endpoint requires HTTP/1.1 |
270 | `hijack_unsupported`, `hijack_failed` | reverse stream setup failed |
271 | `udp_disabled`, `udp_capacity_exceeded`, `udp_port_exhausted` | UDP lease cannot be allocated |
272 | `tcp_port_disabled`, `tcp_port_capacity_exceeded`, `tcp_port_exhausted` | TCP port lease cannot be allocated |
273 | `transport_mismatch` | request does not match the active lease transport |
274 | `internal` | unexpected server failure |