| 1 | --- |
| 2 | title: SDK API |
| 3 | description: Portal SDK endpoints for relay discovery, lease lifecycle, and reverse tunnel streaming. |
| 4 | --- |
| 5 | |
| 6 | # SDK API |
| 7 | |
| 8 | SDK endpoints are the stable lease protocol between a Portal tunnel process and |
| 9 | a relay. Normal JSON endpoints use the shared envelope from |
| 10 | [API Reference](/api-reference). `GET /sdk/connect` is the only SDK endpoint |
| 11 | that switches to a raw stream after a successful HTTP/1.1 response. |
| 12 | |
| 13 | ## Flow |
| 14 | |
| 15 | 1. `GET /sdk/domain` checks relay compatibility, optional ENS support, and optional relay-owned Sui x402 control-plane facilitator support. |
| 16 | 2. `POST /sdk/register/challenge` creates a SIWE challenge for the requested identity. |
| 17 | 3. The SDK signs the returned `siwe_message`. |
| 18 | 4. `POST /sdk/register` exchanges the signature for a lease `access_token`. |
| 19 | 5. The SDK keeps the lease alive with `/sdk/renew` and opens reverse streams with `/sdk/connect`. |
| 20 | 6. `POST /sdk/unregister` removes the lease. |
| 21 | |
| 22 | ## Endpoints |
| 23 | |
| 24 | | Method | Path | Auth | Body | Data | |
| 25 | |--------|------|------|------|------| |
| 26 | | `GET` | `/sdk/domain` | None | none | `DomainResponse` | |
| 27 | | `POST` | `/sdk/register/challenge` | None | `RegisterChallengeRequest` | `RegisterChallengeResponse` | |
| 28 | | `POST` | `/sdk/register` | SIWE signature body | `RegisterRequest` | `RegisterResponse` | |
| 29 | | `POST` | `/sdk/renew` | lease token body | `RenewRequest` | `RenewResponse` | |
| 30 | | `POST` | `/sdk/unregister` | lease token body | `UnregisterRequest` | `{}` | |
| 31 | | `GET` | `/sdk/connect` | lease token header | none | hijacked stream | |
| 32 | |
| 33 | ## Domain |
| 34 | |
| 35 | `GET /sdk/domain` returns: |
| 36 | |
| 37 | | Field | Type | Notes | |
| 38 | |-------|------|-------| |
| 39 | | `protocol_version` | `string` | SDK tunnel protocol version | |
| 40 | | `release_version` | `string` | relay software release | |
| 41 | | `ens` | `ENSStatus` | gasless ENS status | |
| 42 | | `x402` | `X402FacilitatorInfo` | relay-owned Sui x402 control-plane facilitator status | |
| 43 | |
| 44 | `ENSStatus`: |
| 45 | |
| 46 | | Field | Type | |
| 47 | |-------|------| |
| 48 | | `enabled`, `verified` | `boolean` | |
| 49 | | `provider`, `address`, `dnssec_state`, `ds_record`, `message`, `last_error` | `string` | |
| 50 | |
| 51 | `X402FacilitatorInfo`: |
| 52 | |
| 53 | | Field | Type | |
| 54 | |-------|------| |
| 55 | | `enabled` | `boolean` | |
| 56 | | `url`, `network`, `network_name`, `supported_url`, `pay_to` | `string` | |
| 57 | |
| 58 | This object describes the relay's own optional x402 facilitator for |
| 59 | control-plane resources. It is separate from tunnel-owned routed HTTP payments, |
| 60 | which are configured locally by the tunnel process. |
| 61 | |
| 62 | ## Register Challenge |
| 63 | |
| 64 | `RegisterChallengeRequest`: |
| 65 | |
| 66 | | Field | Type | Required | Notes | |
| 67 | |-------|------|----------|-------| |
| 68 | | `identity` | `Identity` | yes | `name` and `address` | |
| 69 | | `metadata` | `LeaseMetadata` | no | public lease metadata | |
| 70 | | `ttl` | `number` | no | requested TTL in seconds | |
| 71 | | `udp_enabled` | `boolean` | no | request UDP transport | |
| 72 | | `tcp_enabled` | `boolean` | no | request dedicated TCP port | |
| 73 | |
| 74 | Overlay-only fields are also accepted by relay-to-relay clients: |
| 75 | `hop_token`, `route_hostname`, `hostname_hash`, and `ech_config_list`. |
| 76 | |
| 77 | `RegisterChallengeResponse`: |
| 78 | |
| 79 | | Field | Type | |
| 80 | |-------|------| |
| 81 | | `challenge_id` | `string` | |
| 82 | | `expires_at` | `string` | |
| 83 | | `siwe_message` | `string` | |
| 84 | |
| 85 | ## Register |
| 86 | |
| 87 | `RegisterRequest`: |
| 88 | |
| 89 | | Field | Type | Required | |
| 90 | |-------|------|----------| |
| 91 | | `challenge_id` | `string` | yes | |
| 92 | | `siwe_message` | `string` | yes | |
| 93 | | `siwe_signature` | `string` | yes | |
| 94 | | `reported_ip` | `string` | no | |
| 95 | |
| 96 | `RegisterResponse`: |
| 97 | |
| 98 | | Field | Type | Notes | |
| 99 | |-------|------|-------| |
| 100 | | `identity` | `Identity` | normalized lease identity | |
| 101 | | `expires_at` | `string` | lease expiry | |
| 102 | | `access_token` | `string` | token for renew, unregister, connect, and signer access | |
| 103 | | `sni_port` | `number` | omitted when not needed | |
| 104 | | `udp_addr`, `tcp_addr` | `string` | omitted when transport is disabled | |
| 105 | | `udp_enabled`, `tcp_enabled` | `boolean` | active transport flags | |
| 106 | |
| 107 | The response does not include a separate `hostname` field. The public hostname |
| 108 | is derived from the registered identity and relay root domain. |
| 109 | |
| 110 | ## Renew And Unregister |
| 111 | |
| 112 | `RenewRequest`: |
| 113 | |
| 114 | | Field | Type | Required | |
| 115 | |-------|------|----------| |
| 116 | | `access_token` | `string` | yes | |
| 117 | | `ttl` | `number` | no | |
| 118 | | `reported_ip` | `string` | no | |
| 119 | | `metadata` | `LeaseMetadata` | no | |
| 120 | |
| 121 | `RenewResponse`: |
| 122 | |
| 123 | | Field | Type | |
| 124 | |-------|------| |
| 125 | | `expires_at` | `string` | |
| 126 | | `access_token` | `string` | |
| 127 | |
| 128 | `UnregisterRequest`: |
| 129 | |
| 130 | | Field | Type | Required | |
| 131 | |-------|------|----------| |
| 132 | | `access_token` | `string` | yes | |
| 133 | |
| 134 | `/sdk/unregister` returns `{}` on success. |
| 135 | |
| 136 | ## Reverse Connect |
| 137 | |
| 138 | `GET /sdk/connect` opens a reverse tunnel stream. |
| 139 | |
| 140 | Requirements: |
| 141 | |
| 142 | | Requirement | Value | |
| 143 | |-------------|-------| |
| 144 | | HTTP version | HTTP/1.1 | |
| 145 | | Header | `X-Portal-Access-Token: <lease access_token>` | |
| 146 | | Connection | keep-alive capable connection that supports hijack | |
| 147 | |
| 148 | On success, the relay writes `HTTP/1.1 200 OK` and hijacks the TCP connection. |
| 149 | There is no JSON response body. Before the hijack, failures still use the |
| 150 | standard JSON error envelope. |
| 151 | |
| 152 | The SDK keeps several ready reverse streams open. When an end user connects to |
| 153 | the lease hostname, the relay claims one ready stream and bridges encrypted |
| 154 | tenant bytes between the browser side and the SDK side. |
| 155 | |
| 156 | ## Relay Overlay |
| 157 | |
| 158 | `/sdk/hop` is reserved for relay-to-relay overlay routing. It accepts |
| 159 | `POST` and `DELETE` with a signed `HopRoute` body and returns `HopRouteResponse` |
| 160 | or `{}`. Normal SDK clients should not call it directly. |