PORTAL v2

API Reference

Portal relay exposes one control-plane API. Frontends, SDK clients, peer relays, and operators all talk to this API, but each group has a small owned surface. Local agent endpoints under /agent/* are not part of the relay API.

JSON Envelope

Matched JSON control endpoints return this envelope:

{
  "ok": true,
  "data": {}
}

Error responses use the same envelope with error instead of data:

{
  "ok": false,
  "error": {
    "code": "unauthorized",
    "message": "unauthorized"
  }
}

data is omitted on error. error is omitted on success.

The envelope does not apply to streaming or delegated endpoints:

PathFormat
/llms.txtMarkdown-formatted plain-text relay and tunnel connection guide
/sdk/connectHTTP/1.1 connection hijack
/v1/signkeyless TLS signer protocol
/api/x402/*relay-owned x402 facilitator response
/api/install.sh, /api/install.ps1, /api/install/bin/*script or binary bytes

Unknown routes may be handled by the frontend fallback or return a normal HTTP 404 outside the envelope.

Auth Schemes

NameUsed byHow it is sent
Nonepublic and challenge endpointsno credential
Admin beareradmin APIAuthorization: Bearer <access_token>
Lease token headerkeyless signer and static cacheX-Portal-Access-Token: <access_token>
Reverse capability headerreverse streamX-Portal-Reverse-Capability: <capability>
Lease token bodylease renew/reverse/unregisterJSON field access_token
Signed descriptorrelay discovery announcesigned RelayDescriptor body

The QUIC datagram backhaul carries the lease access token in a JSON message on its first stream, rather than an HTTP header. Cache endpoints can return plain-text HTTP errors for unsupported methods or exhausted request slots.

Admin auth and SDK lease auth issue different tokens and are not interchangeable. SDK lease registration uses SIWE; relay admin access uses the configured admin token.

Endpoint Groups

Public

MethodPathAuthResponse
GET/Noneselected SPA index.html
GET/HEAD/llms.txtNonerelay and tunnel connection guide
GET/api/healthzNone{ "status": "ok" }
GET/api/stateNonePublicStateResponse
GET/HEAD/api/install.sh, /api/install.ps1Noneinstall script
GET/HEAD/api/install/bin/{slug}Noneinstall binary or redirect

Detailed contracts: SDK API and Admin and Policy API.

SDK

MethodPathAuthBodyResponse
GET/sdk/domainNonenoneDomainResponse
POST/sdk/register/challengeNoneRegisterChallengeRequestRegisterChallengeResponse
POST/sdk/registerSIWE signature bodyRegisterRequestRegisterResponse
POST/sdk/renewlease token bodyRenewRequestRenewResponse
POST/sdk/reverselease token bodyReverseEndpointRequestReverseEndpoint
POST/sdk/unregisterlease token bodyUnregisterRequest{}
GET/sdk/connectreverse capability headernonehijacked stream
POST/sdk/cachelease token headerStaticCacheManifest JSONStaticCacheStatus
PUT/sdk/cachelease token headermanifest and file bytes as multipartStaticCacheStatus
DELETE/sdk/cachelease token headernoneStaticCacheStatus

Admin

MethodPathAuthBodyResponse
POST/api/admin/auth/loginNoneAdminAuthLoginRequestAdminAuthLoginResponse
GET/api/admin/auth/statusOptional admin bearernoneAdminAuthStatusResponse
POST/api/admin/auth/logoutAdmin bearernone{}

/admin itself is a frontend route, not a relay API endpoint.

Payments

Relay /api/x402/* endpoints are optional relay-owned control-plane facilitator endpoints. Enable them with X402_ENABLED=true when a relay operator wants to reserve support for relay resources such as future tunnel registration fees, lease renewal fees, raw TCP/UDP port allocation, or premium capacity. They are served by the embedded gosuda/x402-facilitator handler and do not use the Portal JSON envelope. Portal selects Sui mainnet by default and Sui testnet when X402_TESTNET=true. Portal accepts only USDC gasless stablecoin address-balance payments. X402_PAY_TO is the relay-owned payment recipient.

Relay x402 settings do not affect tunnel paid routes. Tunnel payment recipients and payment networks are local tunnel configuration and are not part of the relay lease API.

Paid routed HTTP tunnels additionally expose /x402/prepare and /x402/client.js on the public tunnel origin. Those are tunnel-owned helper endpoints for app frontends, not relay API routes, and they do not use the /api prefix. /x402/client.js and the transaction-building prepare response are Sui-only. Casper clients consume the protected resource’s 402 requirements, sign with an external Casper x402 SDK, and retry with PAYMENT-SIGNATURE or X-PAYMENT; Portal then delegates verification and settlement to the configured Casper facilitator.

MethodPathAuthBodyResponse
GET/api/x402/supportedNonenonex402 supported kinds
POST/api/x402/verifyNonex402 verify requestx402 verify response
POST/api/x402/settleNonex402 settle requestx402 settle response

Policy

MethodPathAuthBodyResponse
GET/api/policyAdmin bearernonePolicySettings
POST/api/policyAdmin bearerPolicySettingsPolicySettings
GET/api/policy/stateAdmin bearernonePolicyStateResponse
POST/api/policy/leasesAdmin bearerLeasePolicyUpdate{}

Relay

MethodPathAuthResponse
GET/discoveryNoneDiscoveryResponse
POST/discovery/announceSigned descriptorDiscoveryAnnounceResponse
POST/v1/signLease token headerkeyless signer response

Shared Types

Timestamps are JSON-encoded Go time.Time values.

Identity:

FieldTypeNotes
namestringDNS label used by the lease
addressstringEthereum address

LeaseMetadata:

FieldTypeNotes
descriptionstringoptional
ownerstringoptional
thumbnailstringoptional URL or data value
tagsstring[]optional
hidebooleanhidden leases are omitted from the public state

Lease:

FieldType
namestring
addressstring
expires_at, first_seen_at, last_seen_atstring
hostname, canonical_hostnamestring
udp_enabled, tcp_enabledboolean
tcp_addrstring
metadataLeaseMetadata
readynumber

PolicyLease extends Lease with:

FieldType
identity_keystring
bpsnumber
client_ip, reported_ipstring
is_approved, is_banned, is_deniedboolean

PublicStateResponse:

FieldType
leasesLease[]
landing_page_enabledboolean

PolicyStateResponse:

FieldType
policyPolicySettings
leasesPolicyLease[]

PolicyPortSettings:

FieldTypeNotes
enabledbooleanenables the transport
max_leasesnumber0 means unlimited

PolicySettings:

FieldType
approval_mode"auto" or "manual"
landing_page_enabledboolean
udpPolicyPortSettings
tcp_portPolicyPortSettings

LeasePolicyUpdate:

FieldTypeNotes
identity_keystringnormalized name:address key
is_bannedbooleanoptional
is_approvedbooleanoptional
is_deniedbooleanoptional; true also revokes approval
bpsnumberoptional; 0 removes the limit

DiscoveryResponse:

FieldTypeNotes
protocol_versionstringdiscovery protocol version of the serving relay
generated_atstringwhen the response was generated
relaysRelayDescriptor[]signed, routable relay descriptors
incompatible_relaysIncompatibleRelayEntry[]optional; relays contacted directly whose discovery protocol version differs
release_versionstringoptional; release of the serving relay itself
relay_release_versionsmap (peer URL → release)optional; releases directly observed from each peer’s own /discovery

release_version and relay_release_versions are optional, unsigned observation metadata: they never affect routing, trust, signature verification, or compatibility, and older relays omit them. Signed relay identity stays in RelayDescriptor.

Common Errors

CodeMeaning
invalid_jsonrequest body is not valid JSON
invalid_requestrequest shape or value is invalid
method_not_allowedendpoint does not accept the method
unauthorizedcredential is missing, expired, or invalid
feature_unavailablefeature is disabled or not configured
rate_limitedrequest was throttled
hostname_conflictcanonical hostname is inconsistently bound; routing fails closed
lease_not_foundlease token or identity has no active lease
lease_rejectedlease is not currently allowed to route
invalid_addressaddress path or body value is invalid
invalid_modeapproval mode is not auto or manual
http11_onlyendpoint requires HTTP/1.1
hijack_unsupported, hijack_failedreverse stream setup failed
udp_disabled, udp_capacity_exceeded, udp_port_exhaustedUDP lease cannot be allocated
tcp_port_disabled, tcp_port_capacity_exceeded, tcp_port_exhaustedTCP port lease cannot be allocated
transport_mismatchrequest does not match the active lease transport
internalunexpected server failure